Jump to content
NotionCommotion

Transform sequential array to associated array with id/name elements

Recommended Posts

I need to provide a list of time zones which starts with a given string in id/name pairs, and came up with the following solution.

    public function timezones($param) {
        $timezone_identifiers = \DateTimeZone::listIdentifiers();
        $timezone_identifiers_selected = [];
        array_walk($timezone_identifiers, function($name, $id) use (&$timezone_identifiers_selected, $param) {
            if($this->startsWith($name, $param['term'])) $timezone_identifiers_selected[] = ['id'=>$id, 'name'=>$name];
        });
        return [$timezone_identifiers_selected,200];
    }

    private function startsWith($haystack, $needle) {
        return $needle === "" || stripos($haystack, $needle, -strlen($haystack)) !== false;
    }

Before using array_walk, I used array_filter to come up with the  list, but not transform the sequence array to an associated array with id and name elements.  While I am good with the array_walk solution, I would like to know whether this is possible with any single PHP commands.  Kind of like array_columns but in reverse.  Thanks

​        $timezone_identifiers_selected = array_filter($timezone_identifiers, function($val) use($param) {
            return $this->startsWith($val, $param['term']);
        });
 

 

Share this post


Link to post
Share on other sites

In hindsight, I won't be using the DateTimeZone::listIdentifiers id so don't need to make the id/value pairs.  Still, however, interested whether it is possible to perform the array transformation.  Thanks

Share this post


Link to post
Share on other sites

So, like, you want all timezones that start with "America/" or "Asia/" or something? But why do you want the array key? The array is just a normal indexed array. The key isn't useful.

 

I don't see a way to get the result with just one or two built-in functions, but you can do it like

public function timezones($param) {
	return [iterator_to_array((function($ids, $term, $termlen) {
		foreach ($ids as $id => $name) {
			if (strncasecmp($name, $term, $termlen) == 0) {
				yield ['id' => $id, 'name' => $name];
			}
		}
	})(\DateTimeZone::listIdentifiers(), $param['term'], strlen($param['term']))), 200];
}
but that's silly. Your solution is fine, although I don't care for startsWith being its own method (plus strncasecmp is better for this than stripos).

Share this post


Link to post
Share on other sites

Thanks requinix,

 

See my previous "In hindsight..." indicating that I don't need the id.  Typically, all my ajax calls use surrogates and originally I wanted to keep consistent, but came to the same conclusion that the key isn't useful.  And I was also on the fence about startsWith being its own method.

 

Why is strncasecmp better for this than stripos?

Share this post


Link to post
Share on other sites

Right, saw the reply, promptly forgot about it.

 

str(n)(case)cmp compares characters in two strings starting at the beginning. If any character doesn't match then the function stops. If it reaches the end of a string, or the length limit for the 'n' version, then the function stops.

str(i)pos searches for whether one string contains another substring. It starts at one point, checks, and if it doesn't match then advances a character and checks again. And repeats until it finds the substring or reaches the end.

 

In your case you only care if the substring is at the beginning. If it isn't then fail. strncasecmp will do that. stripos will advance to the second character and continue searching, which is wasteful because if it does find the substring you still don't care.

It will also return a false positive because you only care if it returns !==false - it should be ===0 instead.

stripos("America/New_York", "York", -16) === 12

Share this post


Link to post
Share on other sites

While I understand what you originally wanted is no longer needed and you are now just asking for curiosity - I would suggest trying not to "push" a bunch of logic into a single line. Makes it much more difficult to read the code.

 

I would use array_filter() to remove the unwanted entries - then create a new array in the new format you want.

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.