I have two lists of tuples that contain start and end times. All the start and end times are Time structs.
I want to filter out the time_slots that overlap with any booked_slots:
time_slots = [
{~T[09:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]},
{~T[09:00:00], ~T[13:00:00]},
{~T[09:00:00], ~T[21:00:00]}
]
booked_slots = [{~T[14:00:00], ~T[21:00:00]}]
Which should leave us with:
[
{~T[09:00:00], ~T[13:00:00]}
]
I have tried the following method (as adapted from the answer to a previous related question: Compare two lists to find date and time overlaps in Elixir):
Enum.filter(time_slots, fn {time_start, time_end} ->
Enum.any?(booked_slots, fn {booking_start, booking_end} ->
if Time.compare(booking_start, time_start) == :lt do
Time.compare(booking_end, time_start) == :gt
else
Time.compare(booking_start, time_end) == :lt
end
end)
end)
However this returns:
[
{~T[09:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]},
{~T[09:00:00], ~T[21:00:00]}
]
We also need to factor in times that may be equal, but do not overlap. For example:
time_slots = [
{~T[09:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]},
{~T[09:00:00], ~T[21:00:00]},
{~T[09:00:00], ~T[13:00:00]}
]
booked_slots = [{~T[17:00:00], ~T[21:00:00]}]
Should return…
[
{~T[09:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[09:00:00], ~T[13:00:00]}
]
You need to filter out all the time slots having either start or end times inside the booked slot.
Enum.reject(time_slots, fn {ts, te} ->
Enum.any?(booked_slots, fn {bs, be} ->
(Time.compare(ts, bs) != :lt and Time.compare(ts, be) == :lt) or
(Time.compare(te, bs) == :gt and Time.compare(te, be) != :gt) or
(Time.compare(ts, bs) == :lt and Time.compare(te, be) == :gt)
end)
end)
The first condition checks for slots having start time inside the booked slot, the second one checks for those having end time within the booked slots, and the third one checks for those containing the whole booked slot.
Related
I need to calculate a connection value between myself and all of my nearPersons which uses among others the trust value of the link. However, getting the trust values of all links and using these in the computation slows down the running time. I did not succeed to find another way to do it more efficiently. Any suggestions would be highly appreciated!
breed [persons person]
undirected-link-breed [connections connection]
connections-own [trust]
persons-own
[
nearPersons
familiarity
]
to setup
clear-all
setupPersons
setupConnections
updateConnections
reset-ticks
end
setupPersons
create-persons 1000
[
set color black
set grouped false
setxy random-xcor random-ycor
]
end
to setupConnections
ask persons [create-connections-with other persons]
ask connections [ set trust 0.4]
end
to updateConnections
while [(count persons with [grouped = false] / 1000) > 5]
[
let highlyTrusted n-of (2 + random 9) (persons with [grouped = false])
ask highlyTrusted
[
ask my-out-connections with [member? other-end highlyTrusted] [set trust 0.6]
set grouped true
]
]
end
to go
getNearPersons
calculateConnection
forward 1
end
to getNearPersons
ask persons [ set nearPersons other persons in-cone 3 360 ]
end
to calculateConnection
ask persons with [nearPersons != nobody]
[
ask nearPersons
[
let degreeOfTrust [trust] of in-connection-from myself
]
]
end
Here is the model using agentsets of trust rather than links. I think that it will give you identical results, as long as there are only those two trust levels. I made a few other changes in the code - in particular your original while statement would never run as it was written (the quotient was always 1). Most are simply matters of style. Let me know if this is not what you need. Still not a speed demon, about 5 seconds per tick with 1000 persons, but a lot faster than the link version. Note that nPersons is a global so that I could play around with it.
Charles
globals [nPersons]
breed [persons person]
persons-own
[
nearPersons
Trusted
unTrusted
familiarity
grouped
]
to setup
clear-all
set nPersons 1000
ask patches [ set pcolor gray ]
setupPersons
updateConnections
reset-ticks
end
to setupPersons
create-persons nPersons
[
set color black
set grouped false
setxy random-xcor random-ycor
]
ask persons [
set Trusted no-turtles
set unTrusted persons
set familiarity 1
]
end
to updateConnections
while [(count persons with [grouped = false] / nPersons) > 0.2]
[
let highlyTrusted n-of (2 + random 9) (persons)
; so persons can be in more than one trust group and treat all trust groups equally?
ask highlyTrusted
[
set Trusted other highlyTrusted
set grouped true
]
]
end
to go
ask persons [ getNearPersons ]
calculateConnection
ask persons [ forward 1 ]
tick
end
to getNearPersons
ask persons [ set nearPersons other persons in-radius 3 ]
end
to calculateConnection
ask persons with [any? nearPersons]
[
let affiliation []
ask nearPersons
[
let degreeOfTrust ifelse-value (member? myself Trusted) [0.6] [0.4]
; let degreeOfTrust [trust] of in-connection-from myself ;;this line causes netlogo to run very slowly
set affiliation lput (degreeOfTrust * familiarity) affiliation
]
]
end
I had to make a few modifications of your code to make it run, I've included it below. But I believe the real problem is just the scale of what you are doing. With 1000 persons, there are approximately half a million links that you are repeatedly polling, and given the size of your world, the number of nearPersons for each person is likely quite large. Do you really need 1000 persons in your model, and/or do you really need every person to be connected to every other person? The number of links goes up exponentially with the number of persons.
breed [persons person]
undirected-link-breed [connections connection]
connections-own [trust]
persons-own
[
nearPersons
familiarity
grouped
]
to setup
clear-all
setupPersons
show "persons setup"
setupConnections
show "connections setup"
updateConnections
show "connections updated"
reset-ticks
end
to setupPersons
create-persons nPersons
[
set color black
set grouped false
setxy random-xcor random-ycor
]
end
to setupConnections
ask persons [create-connections-with other persons]
ask connections [ set trust 0.4]
end
to updateConnections
while [(count persons with [grouped = false] / 1000) > 5]
[
let highlyTrusted n-of (2 + random 9) (persons)
ask highlyTrusted
[
ask my-out-connections with [member? other-end highlyTrusted] [set trust 0.6]
set grouped true
]
]
end
to go
ask persons [ getNearPersons ]
show mean [count nearPersons] of persons
ask persons [ calculateConnection ]
show "got Connections"
ask persons [ forward 1 ]
tick
end
to getNearPersons
ask persons [ set nearPersons other persons in-cone 3 360 ]
end
to calculateConnection
ask persons with [nearPersons != nobody]
[
let affiliation []
let idx 0
ask nearPersons
[
let degreeOfTrust [trust] of in-connection-from myself ;;this line causes netlogo to run very slowly
set affiliation insert-item idx affiliation (degreeOfTrust * familiarity)
set idx idx + 1
]
]
end
list = ["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02", "HM020", "HM021", "HM022", "HM023", "HM024", "HM025", "HM026", "HM027", "HM028", "HM029", "HM03", "HM030", "HM031", "HM032", "HM033", "HM034", "HM035", "HM036", "HM037", "HM038", "HM039", "HM04", "HM040", "HM041", "HM042", "HM043", "HM044", "HM045", "HM046", "HM047", "HM05", "HM06", "HM07", "HM08", "HM09"]
I want the display the results as ["HM00","HM01","HM002"...] but using sort method it is giving the below results
["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02"]
If every element has a number at the end
list.sort_by { |item| item.scan(/\d*$/).first.to_i }
match that number at the end, take the first one (because scan gives you an array of results), convert it to an integer
simpler
list.sort_by { |item| item[/\d*$/].to_i }
[] already takes the first match
There is a more general solution that will work with most strings that contain groups of numbers
number = /([+-]{0,1}\d+)/;
strings = [ '2', '-2', '10', '0010', '010', 'a', '10a', '010a', '0010a', 'b10', 'b2', 'a1b10c20', 'a1b2.2c2' ]
p strings.sort_by { |item| [item.split(number).each_slice(2).map {
|x| x.size == 1 ? [ x[0], '0' ] : [ x[0], x[1] ] }].map {|y| ret = y.inject({r:[],x:[]}) { |s, z| s[:r].push [ z[0], z[1].to_r]; s[:x].push z[1].size.to_s; s }; ret[:r] + ret[:x] }.flatten
}
You can adjust number to match the types of numbers you want to use: integers, floating point, etc.
There is some extra code to sort equal numbers by length so that '10' comes before '010'.
To play at Stretch the word, I've defined the following words, to try to work at the problem via the same method as this answer:
USING: kernel math sequences sequences.repeating ;
IN: stretch-words
! "bonobo" -> { "b" "bo" "bon" "bono" "bonob" "bonobo" }
: ascend-string ( string -- ascending-seqs )
dup length 1 + iota [ 0 swap pick subseq ] map
[ "" = not ] filter nip ;
! expected: "bonobo" -> "bonoobbooo"
! actual: "bonobo" -> "bbbooonnnooobbbooo"
: stretch-word ( string -- stretched )
dup ascend-string swap zip
[
dup first swap last
[ = ] curry [ dup ] dip count
repeat
] map last ;
stretch-word is supposed to repeat a character in a string by the number of times it's appeared up to that position in the string. However, my implementation is repeating all instances of the 1string it gets.
I have the feeling this is easily implementable in Factor, but I can't quite figure it out. How do I make this do what I want?
Hm... not a great golf, but it works...
First, I made a minor change to ascend-string so it leaves the string on the stack:
: ascend-string ( string -- string ascending-seqs )
dup length 1 + iota [ 0 swap pick subseq ] map
[ "" = not ] filter ;
So stretch-word can work like this:
: stretch-word ( string -- stretched )
ascend-string zip ! just zip them in the same order
[
first2 over ! first2 is the only golf I could make :/
[ = ] curry count ! same thing
swap <array> >string ! make an array of char size count and make it a string
] map concat ; ! so you have to join the pieces
Edit:
I think the problem was using repeat to do the job.
: ascend-string ( string -- seqs )
"" [ suffix ] { } accumulate*-as ;
: counts ( string -- counts )
dup ascend-string [ indices length ] { } 2map-as ;
: stretch-word ( string -- stretched )
[ counts ] keep [ <string> ] { } 2map-as concat ;
"bonobo" stretch-word print
bonoobbooo
indices length could also be [ = ] with count
I'm trying to improve the Sliding Tile Puzzle example by making the starting positions random.
There's a better way to do this--"It is considered bad practice to convert values to strings and join them together to pass to do for evaluation."--but the approach I took was to try to generate Rebol3 source, and then evaluate it. I have it generating correctly, I think:
random/seed now
arr: random collect [ repeat tilenum 9 [ keep tilenum ] ]
hgroup-data: copy {}
repeat pos 9 [
curtile: (pick arr pos)
append hgroup-data either curtile = 9
[ reduce "x: box tilesize gameback " ]
[ rejoin [ { p "} curtile {" } ] ]
if all [(pos // 3) = 0 pos != 9] [ append hgroup-data " return^/" ]
]
print hgroup-data
...outputs something like:
p "4" x: box tilesize gameback p "5" return
p "3" p "7" p "1" return
p "2" p "8" p "6"
...which if I then copy and paste into this part, works correctly:
view/options [
hgroup [
PASTE-HERE
]
] [bg-color: gameback]
However, if I try to do it dynamically:
view/options [
hgroup [
hgroup-data
]
] [bg-color: gameback]
...(also print hgroup-data, do hgroup-data, and load hgroup-data), I get this error:
** GUI ERROR: Cannot parse the GUI dialect at: hgroup-data
...(or at: print hgroup-data, etc., depending on which variation I tried.)
If I try load [ hgroup-data ] I get:
** Script error: extend-face does not allow none! for its face argument
** Where: either if forever -apply- apply init-layout make-layout actor all foreach do-actor unless -apply- apply all build-face -apply- apply init-layout make-layout actor all foreach do-actor if build-face -apply- apply init-layout make-layout actor all foreach do-actor unless make-face -apply- apply case view do either either either -apply-
** Near: either all [
word? act: dial/1
block? body: get dial...
However, if I use the syntax hgroup do [ hgroup-data ], the program runs, but there are no buttons: it appears to be somehow over-evaluated, so that the return values of the functions p and box and so on are put straight into the hgroup as code.
Surely I'm missing an easy syntax error here. What is it?
First, I would say it's better to construct a block directly, instead of constructing a string and converting it to a block. But if you really want to do that, this should do the trick:
view/options compose/only [
hgroup (load hgroup-data)
] [bg-color: gameback]
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I have a large array of DateTimes. For example:
[2013-06-17 19:47:12,
2013-06-17 19:40:01,
2013-06-17 19:42:53,
2013-06-17 19:12:27,
2013-06-17 19:45:42,
2013-06-17 19:14:17]... etc
What I'd like to do is iterate through the array and come up with a bunch of ranges for DateTime objects that are within 5 minutes of one another.
So, the result I would get is:
[
{range_start: 2013-06-17 19:40:01, range_end: 2013-06-17 19:47:12},
{range_start: 2013-06-17 19:12:27, range_end: 2013-06-17 19:14:17},
]
As you can see, the first object in the result set would contain all 4 DateTime objects in the example above by getting the earliest time and latest time and making a range. Likewise for the second.
Basically, what I'm trying to do is group together DateTimes that are within 5 minutes of each other, however I'm not sure how to do it without excessive recursion. For example, once I grab the first DateTime and I find another that is within 5 minutes of it, I then need to find all the other DateTime items that are within 5 minutes of the recently found DateTime.
Start at minute 42
Search 5 minutes before and after
Find another DateTime at minute 44, so now range is 42-44
Need to search 5 minutes before and after range of 42-44 (so anything from 38 to 49)
If I find something at minute 49, then range goes to 42-49
Now search radius is 38 to 54, etc...
Assuming that the time array does not include the unix epoch:
array
.sort
.unshift(Time.at(0))
.each_cons(2)
.slice_before{|t1, t2| t1 + 300 < t2}
.map{|a| min, max = a.map(&:last).minmax; {range_start: min, range_end: max}}
I wasn't going to post this as it was so close to sawa's solution. However this is a working solution whereas his has a couple of major issues.
require 'time'
array = [
'2013-06-17 19:47:12',
'2013-06-17 19:40:01',
'2013-06-17 19:42:53',
'2013-06-17 19:12:27',
'2013-06-17 19:45:42',
'2013-06-17 19:14:17'
].map { |dt| DateTime.parse(dt) }
prev_dt = nil
ranges = array.sort.slice_before do |dt|
is_new_range = prev_dt && (dt - prev_dt) * 1440 > 5
prev_dt = dt
is_new_range
end.map { |range| { range_start: range.first, range_end: range.last } }
ranges.each { |r| p r }
output
{:range_start=>#<DateTime: 2013-06-17T19:12:27+00:00 ((2456461j,69147s,0n),+0s,2299161j)>, :range_end=>#<DateTime: 2013-06-17T19:14:17+00:00 ((2456461j,69257s,0n),+0s,2299161j)>}
{:range_start=>#<DateTime: 2013-06-17T19:40:01+00:00 ((2456461j,70801s,0n),+0s,2299161j)>, :range_end=>#<DateTime: 2013-06-17T19:47:12+00:00 ((2456461j,71232s,0n),+0s,2299161j)>}
This is how I'd go about it:
require 'time'
FIVE_MINUTES = 60 * 5
timestamps = [
'2013-06-17 19:47:12',
'2013-06-17 19:40:01',
'2013-06-17 19:42:53',
'2013-06-17 19:12:27',
'2013-06-17 19:45:42',
'2013-06-17 19:14:17'
].map{ |s| Time.parse(s) }.sort
ranges = [timestamps.first .. timestamps.shift]
loop do
break if timestamps.empty?
if (timestamps.first - ranges.last.max) <= FIVE_MINUTES
ranges[-1] = (ranges.last.min .. timestamps.shift)
else
ranges << (timestamps.first .. timestamps.shift)
end
end
pp ranges.map{ |r|
Hash[
:range_start, r.min,
:range_end, r.max
]
}
Which is an array of hashes:
[
{
:range_start => 2013-06-17 19:12:27 -0700,
:range_end => 2013-06-17 19:14:17 -0700
},
{
:range_start => 2013-06-17 19:40:01 -0700,
:range_end => 2013-06-17 19:47:12 -0700
}
]
I converted the DateTime strings to Time values because you get an integer in seconds when subtracting them. That worked well when comparing to FIVE_MINUTES. If you need DateTime objects, you can convert them easily using:
pp ranges.map{ |r|
Hash[
:range_start, r.min.to_datetime,
:range_end, r.max.to_datetime
]
}
Which now looks like:
[
{
:range_start=> #<DateTime: 2013-06-17T19:12:27-07:00 ((2456462j,7947s,0n),-25200s,2299161j)>,
:range_end=> #<DateTime: 2013-06-17T19:14:17-07:00 ((2456462j,8057s,0n),-25200s,2299161j)>
},
{
:range_start=> #<DateTime: 2013-06-17T19:40:01-07:00 ((2456462j,9601s,0n),-25200s,2299161j)>,
:range_end=> #<DateTime: 2013-06-17T19:47:12-07:00 ((2456462j,10032s,0n),-25200s,2299161j)>
}
]
I sorted the array because that made it pretty straightforward to find values that were within the five minute boundaries of each other. That results in the ranges being sorted also.