Friday, May 6, 2011

Selectively flattening a PHP array according to parent keys

I need to selectively flatten an array in PHP, but do it selectively. If a key matches a pattern then all sub elements below that key should be included in the 'flat' output.

SO If I had a catalogue of music:

=> array of albums => each of which is an array of song titles

Then I could search for a string, and would get a flat array in reponse. SO if I searched for 'sun' then I would get the entire catalogue for any artist with 'sun' in their name, plus the albums for other artists where 'sun' was in the album name.

Hopefully that makes sense.

Anyone got any thoughts?

From stackoverflow
  • Is there a reason you're not using a database to store what sounds like a significant amount of info? It would be fairly simple to write a query in SQL to pull the data out that you want.

    R. Bemrose : +1: That was my first thought, too.
  • Do you mean something like that?

    $a = array(
        "Sunny" => "Bla-bla1",
        "Sun" => "Bla-bla2",
        "Sunshine" => "Bla-bla3",
    );
    
    foreach ($a as $k => $v) 
    {
     // check here whenever you want for $k
     // apply something to $v
     if ( ... )
          $b[$i++] = $v;
    }
    
  • Ok, I'm going to assume your data looks like this:

    $data = array(
        "Bill Withers" => array (
            "Lovely Day",
            "Use Me",
            "Ain't No Sunshine"
        ),
        "Fleet Foxes" => array (
            "Sun It Rises",
            "White Winter Hymnal"
        ),
        "Billy Joel" => array (
            "Piano Man"
        )
    );
    

    ...and that given the input "Bill", you want the output: ["Lovely Day", "Use Me", "Ain't No Sunshine", "Piano Man"]. Here's one way you could do it.

    function getSongs($data, $searchTerm) {
        $output = array();
        foreach ($data as $artist => $songs) {
            if (stripos($artist, $searchTerm) !== false)) {
                $output = array_merge($output, $songs);
            }
        }
        return $output;
    }
    

    ...I'll also assume you've got a good reason to not use a database for this.

  • $sample_data = array(  
     'artist_name' => array(  
      'album_name' => array(  
       'song_name',  
       'other_mp3_id_info'  
       )
      )  
     );
    
    function s2($needle,$haystack) {
    
     $threads = array();
     foreach ($haystack as $artist_name => $albums) {
      if (stripos($artist_name, $needle) !== false)) {
       $threads[] = $haystack[$artist_name]; //Add all artist's albums
      } else {
       foreach ($albums as $album_name => $songs) {
        if (stripos($album_name, $needle) !== false)) {
         $threads[$artist_name][] = $haystack[$album_name]; //add matching album
        }
       }
      }
     }
     return $threads;
    }
    
  • To build off NickF's work, assuming your data looks like his, I'd write the function this way.

    function getSongs($data, $searchTerm) {
        foreach ($data as $artist => $songs) {
            if (stripos($artist, $searchTerm) !== false)) {
                $output[$artist] = $songs;
            }
        }
        return $output or null;
    }
    

    The results of this function, when no matches are found will obviously return null instead of a blank array; when several matches are found they will then be grouped by their artist. I find direct assignment, $output[$artist] = $songs, to provide more predictable results than array_merge in my own experience. (This also preserves the artist for outputting that data.)

    Like NickF said, I would assume you've good reason to not do this with a database? SQL for this would be very simple, such as,

    SELECT artist, song FROM songs WHERE artist LIKE '%Bill%' GROUP BY artist;
    
  • You can use preg_grep for the searches, be sure to sanitize the input, though:

    $matchingArtists = preg_grep('/' . preg_quote($searchString) . '/', array_keys($data));
    $matchingSongs = array();
    foreach ($data as $artist => $songs) {
        $matchingSongs = array_merge($matchingSongs, preg_grep('/' . preg_quote($searchString) . '/', $songs));
    }
    

0 comments:

Post a Comment