23
$\begingroup$

I am using the following code to easily generate a row of images of all eight planets of our Solar System:

Labeled[# // Text, AstronomicalData[#, "Image"]] & /@ 
  AstronomicalData["Planet"] // Row

I like the above code because it is concise and gives nice output. However, if I wanted to do something similar for the moons in our Solar System, I run into the problem that only some moons have images. If I run the following line of code, I get a somewhat sloppy output with Missing Image errors embedded.

Labeled[# // Text, AstronomicalData[#, "Image"]] & /@ 
  AstronomicalData["PlanetaryMoon"] // Row

I was wondering if there is a simple way to Map over a list of data conditionally? That is, if an element in a list does not meet a specific condition then skip over it. Note that I'm looking to avoid an explicit loop with an If construct. I really don't mind using a loop with an If statement, but I was curious if there was a concise idiom that anyone could shed some light on.

$\endgroup$
5
  • 2
    $\begingroup$ This is a beautiful illustration of many ways to solve a problem in Mathematica! $\endgroup$
    – Cassini
    Commented Feb 3, 2012 at 4:52
  • $\begingroup$ There is a lot of truly excellent feedback on this page. I learned something new from each person's answer. Thank you everyone for contributing. $\endgroup$ Commented Feb 3, 2012 at 5:06
  • $\begingroup$ Also, thank you rcollyer for cleaning up my post :) $\endgroup$ Commented Feb 3, 2012 at 5:07
  • $\begingroup$ The more I look at these answers the more I see just how much experience you guys have. Seriously a dizzying array of different approaches. I've learned SOO much more looking up the strange code I've seen here than what I originally came here to learn :D Thanks again everyone! $\endgroup$ Commented Feb 3, 2012 at 5:18
  • $\begingroup$ You're welcome, and welcome to the community. $\endgroup$
    – rcollyer
    Commented Feb 3, 2012 at 14:10

8 Answers 8

33
$\begingroup$

Here's an option which only passes moons with images to the Labeled function:

Labeled[# // Text, AstronomicalData[#, "Image"]] & /@ 
  Select[AstronomicalData["PlanetaryMoon"], 
   ImageQ[AstronomicalData[#, "Image"]] &] // Row

enter image description here

$\endgroup$
1
  • 2
    $\begingroup$ This was EXACTLY what I was looking for. Your example uses purely functional programming, gets straight to the point, and avoids any unsightly workarounds. Thank you!! $\endgroup$ Commented Feb 3, 2012 at 5:03
15
$\begingroup$

You will "test" if Missing["NotAvailable"] is returned every time anyway. So a "concise" approach is to filter them out at the end:

Cases[Labeled[# // Text, AstronomicalData[#, "Image"]] & /@ 
   AstronomicalData["PlanetaryMoon"], _[_, _Image]] // Row

I'd be curious to see a more "concise" piece of code. Alternatively instead of using exact pattern _[_, _Image] you could just use _Image and specify all levels in expression to search for that pattern:

Cases[Labeled[# // Text, AstronomicalData[#, "Image"]] & /@ 
   AstronomicalData["PlanetaryMoon"], _Image, Infinity] // Row

But this is a few characters longer ;-)

$\endgroup$
5
  • 3
    $\begingroup$ Or complementarily, DeleteCases[(* stuff *), _Missing]... $\endgroup$ Commented Feb 3, 2012 at 4:41
  • $\begingroup$ Thank you for posting this, Vitaliy. I've learned a lot from your example. I would say that this answer comes in second place only because it requires more computation than the answer provided by David Skulsky. Mr. Skulsky's answer avoids running Map on elements unless they meet the criteria. In your answer all elements are computed and then filtered. Either way, this is a great answer. Thank you! $\endgroup$ Commented Feb 3, 2012 at 5:24
  • 1
    $\begingroup$ +1 for Vitaly's answer because his approach only calls AstronomicalData[#,"Image"] once for each moon, whereas my approach calls it twice. $\endgroup$
    – Cassini
    Commented Feb 3, 2012 at 5:32
  • $\begingroup$ @J.M. I was rather referring to ` _Missing` part - it won't delete because Labeled wrapped around ` _Missing`. You cracked me up with the "idiom" joke ;) $\endgroup$ Commented Feb 3, 2012 at 5:33
  • $\begingroup$ Still, the pattern can be changed accordingly. Or, use DeleteCases[] before any further processing's done. :) Anyway, at least you got my intent. $\endgroup$ Commented Feb 3, 2012 at 5:48
15
$\begingroup$

This is absurd in how it looks, but in this case I don't think skipping over the missing images is necessary. Instead, we are going to redefine what Missing means by attaching an upvalue to it using UpSetDelayed (^:=), as follows

Block[{Missing},
  Labeled[_, _Missing] ^:= Sequence[];
  Labeled[# // Text, AstronomicalData[#, "Image"]]
] & /@ AstronomicalData["PlanetaryMoon"] // Row

and returns what you expect. Block allows you to temporarily redefine what a Symbol means, and in this case, I set Missing["NotAvailable"] to return a Sequence[]. An empty sequence has the special property that it will truncate a list it is found in, for example

{a, b, Sequence[], c}

returns

{a, b, c}

So, by redefining Missing to return an empty Sequence, the code shortens the list for you.

Also, here's a purely functional approach that does not require accessing the database more than twice: once for the list of the moons and once for the image.

labelledMoon[name_String, img_?ImageQ] := Labeled[Text@name, img]
labelledMoon[_, _Missing] := Sequence[]
labelledMoon[#, AstronomicalData[#, "Image"]] & /@ 
  AstronomicalData["PlanetaryMoon"] // Row

This employs the feature of an empty Sequence to automatically shorten the list. Here's a self contained version:

With[{name=#, img = AstronomicalData[#, "Image"]},
  If[Head[img]=!=Missing, 
     Labeled[Text@name, img],
     Unevaluated[Sequence[]]
  ]]& /@  AstronomicalData["PlanetaryMoon"] // Row

which gives the same result. Note, this required the use of Unevaluated to ensure that the empty Sequence was inserted properly into the list.

A functional alternative to Unevaluated[Sequence[]] is to use a SlotSequence. As ##&[1,2,3] returns Sequence[1,2,3], then an empty Sequence can be returned via ##&[]. Here's the shortened version:

With[{name=#, img = AstronomicalData[#, "Image"]},
  If[Head[img]=!=Missing, Labeled[Text@name, img], ##&[]]
]& /@  AstronomicalData["PlanetaryMoon"] // Row

All 4 versions of the code output:

enter image description here

$\endgroup$
0
14
$\begingroup$

In situations like your, where you don't know the number of elements in your result in advance, two functions become very handy: Reap and Sow. The approach is to Sow an element you want in your list deep inside the code and to Reap them at the outside.

To get all primes below 11 you can do

Reap[Table[If[PrimeQ[i], Sow[i]], {i, 10}]]

(*
  {{Null, 2, 3, Null, 5, Null, 7, Null, Null, Null}, {{2, 3, 5, 7}}}
*)

Note that Reap gives you two lists, the first one is your original Table result which contains Null when a number is not prime and the last list contains only the Sowed elements. Therefore, your moons are extracted by

Row[Last[Last[Reap[With[{img = AstronomicalData[#, "Image"]},
       If[Head[img] =!= Missing,
        Sow[Labeled[# // Text, img]]]] & /@ 
     AstronomicalData["PlanetaryMoon"]]]]]
$\endgroup$
13
$\begingroup$

Here is yet another approach:

Cases[
  Thread[ AstronomicalData["PlanetaryMoon", #]& /@ {"Name", "Image"} ],
  {n_, i_Image} :> Labeled[Text@n, i]
] // Row
$\endgroup$
8
$\begingroup$

In version 10, the GeneralUtilities context provides a MapIf function that does exactly this:

<<GeneralUtilities`
MapIf[f, EvenQ, Range@10]
(* {1, f[2], 3, f[4], 5, f[6], 7, f[8], 9, f[10]} *)

As with Map, MapIf can also be used in an operator form with MapIf[f, test].

$\endgroup$
6
$\begingroup$

For shorter lists you can use patterns. For example you want to apply a function only to the even numbers of a list:

n = RandomInteger[{0, 9}, 8]
{5, 2, 1, 6, 8, 5, 7, 7}
n /. i_?EvenQ -> f[i]
{5, f[2], 1, f[6], f[8], 5, 7, 7}

In your case, you would have to replace the EvenQ by something like a HasImageQ, and f by a function that determines how matching entries have to be displayed.

You may also want to have a look at Cases, which is capable of doing a similar thing.

$\endgroup$
5
$\begingroup$

Seems like I'm a year late to this fun filled party, but here's my take:

names = AstronomicalData["PlanetaryMoon"];
data = {names, AstronomicalData[#, "Image"] & /@ names};
MapThread[Labeled[Text@#1, #2] &, data] /. Labeled[_, _Missing] -> Sequence[] // Row
$\endgroup$
6
  • $\begingroup$ For what it's worth you don't need pattern names; Labeled[_, _Missing] will work. $\endgroup$
    – Mr.Wizard
    Commented Apr 15, 2013 at 0:42
  • $\begingroup$ @Mr.Wizard, Ah yes, since I don't refer to them again. Edited to include your suggestion, thanks. $\endgroup$
    – RunnyKine
    Commented Apr 15, 2013 at 0:45
  • 1
    $\begingroup$ Also, if you are open to funny looking patterns: DeleteCases[MapThread[Labeled[Text@#, #2] &, data], _~_~_Missing] // Row :-) $\endgroup$
    – Mr.Wizard
    Commented Apr 15, 2013 at 0:46
  • $\begingroup$ @Mr.Wizard, you lost me there, you'll have to explain _~_~_Missing to me. $\endgroup$
    – RunnyKine
    Commented Apr 15, 2013 at 0:48
  • $\begingroup$ It's equivalent to _[_, _Missing] but in infix form, and without the spacing needed to make that form readable. I don't recommend it in practice! For another humorous pattern see this. Frightening, isn't it? :o) $\endgroup$
    – Mr.Wizard
    Commented Apr 15, 2013 at 0:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.