ID:195082
 
//  Title: Closest of Type
// Credit to: Hiead
// Contributed by: Hiead

/*
This snippet shows a procedure that returns a list of the closest
objects of a specified type. It starts off by determining the distance
from the central object to the nearest type specified, and then it loops
through a list of atoms in that distance and pulls out atoms of the
specified type.

The first two parameters resemble those of several built-in DM
procs: maxDistance is the maximum distance from which a "closest" atom
may be included, and center holds a reference to the object from which
the distances to other objects must be compared.

The third parameter is the cornerstone of the whole proc; it specifies
the type of which the "closest" objects must be. The fourth parameter is
just a specifier as to whether the central object itself should be included
in the filter. If true, it should be noted that the closest range will
then be 0, and only objects sharing the same location and of the specified
type will be returned.
*/


proc
Closest(maxDistance=world.view, center=usr, checkType=/atom, useSelf=FALSE)
if(!isnum(maxDistance) || !maxDistance)
maxDistance = 5

. = new/list // Will return a list
var/closest = maxDistance
var/list/surrounding

if(useSelf) // Include center in test?
surrounding = range(closest, center)
else
surrounding = range(closest, center) - center

for(var/atom in surrounding)
if(istype(atom, checkType))
var/dist = get_dist(center, atom)
if(dist < closest)
closest = dist

if(useSelf) // Include center in return?
surrounding = range(closest, center)
else
surrounding = range(closest, center) - center

for(var/atom in surrounding)
if(istype(atom, checkType))
. += atom


///*
// Testing Code/Sample Implementation:

world
maxx = 13
maxy = 13

mob/Login()
..()
loc = locate(7, 7, 1)

mob/verb
CreateMob()
var/global/count = 0
var/mob/M = new(locate(rand(1, world.maxx), rand(1, world.maxy), 1))
M.name = "Mob #[++count]"
src << "[M.name] created at ([M.x],[M.y])"

ClosestMobs()
var/list/closestMobs = Closest(6, src, /mob)
src << "<hr>"
if(!closestMobs.len)
src << "No mobs are nearby."
else for(var/mob/M in closestMobs)
src << "[M.name] @ distance = [get_dist(src, M)]"
src << "<hr>"

//*/


Hiead
I'm not sure I'd go about this the same way you have. I'd probably make them send the list instead of size (also keep in mind world.view can be set to "NxN" sizes). It makes things easier on you, and they can decide if they want this based on vision, sound, or just range. =)

proc/Closest(list/things, atom/center, type)
for(var/atom/A in things)
if(istype(A,type))
if(.)
if(get_dist(center,A)==get_dist(center,.[1]))
.+=A
else if(get_dist(center,A)<get_dist(center,.[1]))
.=list(A)
else
.=list(A)



///*
// STOLEN Testing Code/Sample Implementation:

world
maxx = 13
maxy = 13

mob/Login()
..()
loc = locate(7, 7, 1)

mob/verb
CreateMob()
var/global/count = 0
var/mob/M = new(locate(rand(1, world.maxx), rand(1, world.maxy), 1))
M.name = "Mob #[++count]"
src << "[M.name] created at ([M.x],[M.y])"

ClosestMobs()
var/list/closestMobs = Closest(orange(6), src, /mob)
src << "<hr>"
if(!closestMobs||!closestMobs.len)
src << "No mobs are nearby."
else for(var/mob/M in closestMobs)
src << "[M.name] @ distance = [get_dist(src, M)]"
src << "<hr>"

//*/
In response to YMIHere
Hm, actually the sending of the list itself is a really good idea, though I still think that I prefer some of my method to yours. Not the input part; no, I like that. But the actual inside of the proc, though shorter, seems to me like it was handled less efficiently.

I think it would be better to determine the distance to the closest objects before appending them to the list, rather than constantly creating new lists each time one is found that is closer. For a large value of "things," I could imagine this drastically impacting efficiency. A modified version of my original, implementing your parameter setup and what I've just mentioned, might look like this:
proc/Closest(list/things=new, center=usr, checkType=/atom)
if(!istype(things))
return things

. = new/list
var/distance = -1

for(var/atom in things)
if(istype(atom, checkType))
var/dist = get_dist(center, atom)
if(dist < distance || distance < 0)
distance = dist

for(var/atom in things)
if(istype(atom, checkType) && get_dist(center, atom) == distance)
. += atom


Hiead
In response to Hiead
To confirm, with 100 mobs yours performs significantly faster, while mine is only slightly faster with none. Yours takes the cake. =)

                        0 mobs
Proc                          SCPUTime TCPUTime RTime Calls

/proc/Closest_Hiead           0.150    0.150    0.150 100
/proc/Closest                 0.110    0.110    0.110 100
/mob/verb/ClosestMobs         0.040    0.150    0.150 100
/mob/verb/ClosestMobsHiead    0.010    0.160    0.160 100

                       100 mobs
Proc                          SCPUTime TCPUTime RTime Calls

/proc/Closest                 0.351    0.351    0.351 100
/proc/Closest_Hiead           0.290    0.290    0.290 100
/mob/verb/ClosestMobs         0.120    0.471    0.471 100
/mob/verb/ClosestMobsHiead    0.090    0.380    0.390 100