سؤال

كيف لي أن أكتب اثنين من المهام التي من شأنها أن تأخذ سلسلة والعودة إذا كان يبدأ مع الحرف المحدد/string أو ينتهي ؟

على سبيل المثال:

$str = '|apples}';

echo startsWith($str, '|'); //Returns true
echo endsWith($str, '}'); //Returns true
هل كانت مفيدة؟

المحلول

function startsWith($haystack, $needle)
{
     $length = strlen($needle);
     return (substr($haystack, 0, $length) === $needle);
}

function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if ($length == 0) {
        return true;
    }

    return (substr($haystack, -$length) === $needle);
}

استخدم هذا إذا كنت لا ترغب في استخدام التعابير المنطقية.

نصائح أخرى

ويمكنك استخدام substr_compare وظيفة للتحقق بدء مع وينتهي مع:

function startsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function endsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

في هذا يجب أن يكون واحدا من أسرع الحلول في PHP 7 ( النصي القياسي ) . اختبار ضد أكوام التبن 8KB، ومختلف الإبر طول والحالات كاملة أو جزئية وأي تطابق. strncmp هو لمسة أسرع ليبدأ مع ولكنه لا يمكن أن تحقق الغايات مع.

وتحديث 23 أغسطس 2016

تحليل وظائف

function substr_startswith($haystack, $needle) {
    return substr($haystack, 0, strlen($needle)) === $needle;
}

function preg_match_startswith($haystack, $needle) {
    return preg_match('~' . preg_quote($needle, '~') . '~A', $haystack) > 0;
}

function substr_compare_startswith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}

function strpos_startswith($haystack, $needle) {
    return strpos($haystack, $needle) === 0;
}

function strncmp_startswith($haystack, $needle) {
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function strncmp_startswith2($haystack, $needle) {
    return $haystack[0] === $needle[0]
        ? strncmp($haystack, $needle, strlen($needle)) === 0
        : false;
}

تحليل الاختبارات

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';
    $test_cases[] = [
        random_bytes(random_int(1, 7000)),
        random_bytes(random_int(1, 3000)),
    ];
}
echo "done!\n";


$functions = ['substr_startswith', 'preg_match_startswith', 'substr_compare_startswith', 'strpos_startswith', 'strncmp_startswith', 'strncmp_startswith2'];
$results = [];

foreach($functions as $func) {
    $start = microtime(true);
    foreach($test_cases as $tc) {
        $func(...$tc);
    }
    $results[$func] = (microtime(true) - $start) * 1000;
}

asort($results);

foreach($results as $func => $time) {
    echo "$func: " . number_format($time, 1) . " ms\n";
}

تحليل نتائج (PHP 7.0.9)

و(الترتيب الأسرع إلى الأبطأ)

strncmp_startswith2: 40.2 ms
strncmp_startswith: 42.9 ms
substr_compare_startswith: 44.5 ms
substr_startswith: 48.4 ms
strpos_startswith: 138.7 ms
preg_match_startswith: 13,152.4 ms

تحليل نتائج (PHP 5.3.29)

و(الترتيب الأسرع إلى الأبطأ)

strncmp_startswith2: 477.9 ms
strpos_startswith: 522.1 ms
strncmp_startswith: 617.1 ms
substr_compare_startswith: 706.7 ms
substr_startswith: 756.8 ms
preg_match_startswith: 10,200.0 ms

startswith_benchmark.php

وجميع الإجابات يبدو حتى الآن أن تفعل الكثير من العمل لا لزوم له، strlen calculations، string allocations (substr)، وما إلى ذلك وظائف 'strpos' و'stripos' تعود مؤشر التواجد الأول $needle في $haystack:

function startsWith($haystack,$needle,$case=true)
{
    if ($case)
        return strpos($haystack, $needle, 0) === 0;

    return stripos($haystack, $needle, 0) === 0;
}

function endsWith($haystack,$needle,$case=true)
{
    $expectedPosition = strlen($haystack) - strlen($needle);

    if ($case)
        return strrpos($haystack, $needle, 0) === $expectedPosition;

    return strripos($haystack, $needle, 0) === $expectedPosition;
}
function startsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
}

function endsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
}

<القوي> الائتمان إلى : ل

تحقق مما إذا تنتهي السلسلة مع آخر سلسلة

معرفة ما اذا كان يبدأ سلسلة مع آخر سلسلة

وظائف التعابير المنطقية أعلاه، ولكن مع غيرها من القرص اقترح أيضا أعلاه:

 function startsWith($needle, $haystack) {
     return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack);
 }

 function endsWith($needle, $haystack) {
     return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
 }

وهذا السؤال له بالفعل العديد من الإجابات، ولكن في بعض الحالات يمكنك تسوية لشيء أبسط من كل منهم. إذا كانت السلسلة التي تبحث عنها هو معروف (ضمنية)، يمكنك استخدام التعابير العادية دون أي نقلا عن غيرها.

ومعرفة ما اذا كان يبدأ سلسلة مع 'ABC':

preg_match('/^ABC/', $myString); // "^" here means beginning of string

وينتهي مع 'ABC':

preg_match('/ABC$/', $myString); // "$" here means end of string

في حالتي بسيطة، كنت أرغب في معرفة ما اذا تنتهي السلسلة مع الخط المائل:

preg_match('#/$#', $myPath);   // Use "#" as delimiter instead of escaping slash

وميزة: لأنه قصير جدا وبسيطة، لم يكن لديك لتحديد وظيفة (مثل endsWith()) كما هو مبين أعلاه

.

ولكن مرة أخرى - وهذا ليس حلا لكل حالة، فقط هذا واحد محدد جدا

إذا سرعة المهم بالنسبة لك، وهذه محاولة. (وأعتقد أنه هو أسرع طريقة)

ويعمل فقط من أجل السلاسل وإذا $ كومة قش هو 1 شخصية فقط

function startsWithChar($needle, $haystack)
{
   return ($needle[0] === $haystack);
}

function endsWithChar($needle, $haystack)
{
   return ($needle[strlen($needle) - 1] === $haystack);
}

$str='|apples}';
echo startsWithChar($str,'|'); //Returns true
echo endsWithChar($str,'}'); //Returns true
echo startsWithChar($str,'='); //Returns false
echo endsWithChar($str,'#'); //Returns false

وهنا اثنين من الوظائف التي لا إدخال سلسلة مؤقتة، والتي يمكن أن تكون مفيدة عندما الإبر كبيرة إلى حد كبير:

function startsWith($haystack, $needle)
{
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function endsWith($haystack, $needle)
{
    return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

أدرك أن هذا قد انتهى ، ولكن قد ترغب في النظر في strncmp كما أنه يسمح لك لوضع طول السلسلة مقارنة ضد ذلك:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncmp($haystack, $needle, strlen($needle)) == 0;
}    

أسرع endsWith () الحل:

# Checks if a string ends in a string
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

تحليل المعيار:

# This answer
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

# Accepted answer
function endsWith2($haystack, $needle) {
    $length = strlen($needle);

    return $length === 0 ||
    (substr($haystack, -$length) === $needle);
}

# Second most-voted answer
function endsWith3($haystack, $needle) {
    // search forward starting from end minus needle length characters
    if ($needle === '') {
        return true;
    }
    $diff = \strlen($haystack) - \strlen($needle);
    return $diff >= 0 && strpos($haystack, $needle, $diff) !== false;
}

# Regex answer
function endsWith4($haystack, $needle) {
    return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
}

function timedebug() {
    $test = 10000000;

    $time1 = microtime(true);
    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith('TestShortcode', 'Shortcode');
    }
    $time2 = microtime(true);
    $result1 = $time2 - $time1;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith2('TestShortcode', 'Shortcode');
    }
    $time3 = microtime(true);
    $result2 = $time3 - $time2;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith3('TestShortcode', 'Shortcode');
    }
    $time4 = microtime(true);
    $result3 = $time4 - $time3;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith4('TestShortcode', 'Shortcode');
    }
    $time5 = microtime(true);
    $result4 = $time5 - $time4;

    echo $test.'x endsWith: '.$result1.' seconds # This answer<br>';
    echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer<br>';
    echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer<br>';
    echo $test.'x endsWith4: '.$result3.' seconds # Regex answer<br>';
    exit;
}
timedebug();

تحليل نتائج المعيار:

10000000x endsWith: 1.5760900974274 seconds # This answer
10000000x endsWith2: 3.7102129459381 seconds # Accepted answer
10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer
10000000x endsWith4: 2.1521229743958 seconds # Regex answer

يمكنك استخدام strpos و strrpos

$bStartsWith = strpos($sHaystack, $sNeedle) == 0;
$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);

وقصيرة وسهلة الفهم واحد المتشددين دون التعابير العادية.

وstartsWith () هي على التوالي إلى الأمام.

function startsWith($haystack, $needle) {
   return (strpos($haystack, $needle) === 0);
}

وendsWith () يستخدم strrev يتوهم قليلا وبطيئا ():

function endsWith($haystack, $needle) {
   return (strpos(strrev($haystack), strrev($needle)) === 0);
}

وهنا نسخة متعددة البايت الآمن للإجابة مقبولة، وأنها تعمل على ما يرام بالنسبة UTF-8 سلاسل:

function startsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return (mb_substr($haystack, 0, $length, 'UTF-8') === $needle);
}

function endsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return $length === 0 ||
        (mb_substr($haystack, -$length, $length, 'UTF-8') === $needle);
}

مع التركيز على startswith ، إذا كنت متأكدا من السلاسل ليست فارغة ، إضافة اختبار في أول شار قبل المقارنة ، strlen ، وما إلى ذلك ، سرعات الأمور قليلا:

function startswith5b($haystack, $needle) {
    return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;
}

هو بطريقة أو بأخرى (20%-30%) بشكل أسرع.إضافة آخر شار الاختبار ، مثل $قش{1}===$إبرة{1} لا يبدو أن تسريع الأمور كثيرا ، بل قد تبطئ.

=== يبدو أسرع من == الشرطي المشغل (a)?b:c يبدو أسرع من if(a) b; else c;


لمن يسأل "لماذا لا تستخدم لاتساوي?" الدعوة حلول أخرى "العمل لا لزوم لها"


لاتساوي سريعة ولكنها ليست الأداة المناسبة لهذا المنصب.

فهم هنا هو محاكاة بسيطة كمثال:

Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c

ماذا يفعل الكمبيوتر "داخل" ؟

    With strccmp, etc...

    is a===b? NO
    return false



    With strpos

    is a===b? NO -- iterating in haysack
    is a===c? NO
    is a===d? NO
    ....
    is a===g? NO
    is a===g? NO
    is a===a? YES
    is 1===1? YES -- iterating in needle
    is 2===3? YES
    is 4===4? YES
    ....
    is 8===8? YES
    is c===x? NO: oh God,
    is a===1? NO -- iterating in haysack again
    is a===2? NO
    is a===3? NO
    is a===4? NO
    ....
    is a===x? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    ...
    ... may many times...
    ...
    is a===b? NO
    is a===a? YES -- iterating in needle again
    is 1===1? YES
    is 2===3? YES
    is 4===4? YES
    is 8===8? YES
    is c===c? YES YES YES I have found the same string! yay!
    was it at position 0? NOPE
    What you mean NO? So the string I found is useless? YEs.
    Damn.
    return false

على افتراض strlen لا تكرار السلسلة بأكملها (ولكن حتى في هذه الحالة) هذه ليست مريحة على الإطلاق.

وآمل أن الجواب أدناه قد تكون فعالة، وكذلك بسيط:

$content = "The main string to search";
$search = "T";
//For compare the begining string with case insensitive. 
if(stripos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the begining string with case sensitive. 
if(strpos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case insensitive. 
if(stripos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case sensitive. 
if(strpos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';

وأنا عادة في نهاية المطاف الذهاب مع مكتبة مثل التأكيد-فب هذه الأيام.

require_once("vendor/autoload.php"); //use if needed
use Underscore\Types\String; 

$str = "there is a string";
echo( String::startsWith($str, 'the') ); // 1
echo( String::endsWith($str, 'ring')); // 1   

مكتبة كاملة من وظائف مفيدة أخرى.

الإجابة بواسطة mpen غير دقيق بشكل لا يصدق، ولكن، للأسف، المؤشر قدمت له الرقابة مهمة جدا وضارة.

ولأن كل بايت في الإبر وأكوام التبن هو عشوائي تماما، واحتمال أن زوج إبرة كومة قش سوف تختلف على البايت الأول جدا هو 99.609375٪، وهو ما يعني أنه، في المتوسط، نحو 99609 من 100000 أزواج سوف تختلف على البايت الأول جدا. وبعبارة أخرى، فإن المعيار هو منحاز بشكل كبير نحو تطبيقات startswith التي تحقق البايت الأول صراحة، كما strncmp_startswith2 يفعل.

إذا بدلا من ذلك يتم تنفيذ حلقة المدرة للاختبار كما يلي:

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';

    $haystack_length = random_int(1, 7000);
    $haystack = random_bytes($haystack_length);

    $needle_length = random_int(1, 3000);
    $overlap_length = min(random_int(0, $needle_length), $haystack_length);
    $needle = ($needle_length > $overlap_length) ?
        substr($haystack, 0, $overlap_length) . random_bytes($needle_length - $overlap_length) :
        substr($haystack, 0, $needle_length);

    $test_cases[] = [$haystack, $needle];
}
echo " done!<br />";

ونتائج المؤشر تروي قصة مختلفة قليلا:

strncmp_startswith: 223.0 ms
substr_startswith: 228.0 ms
substr_compare_startswith: 238.0 ms
strncmp_startswith2: 253.0 ms
strpos_startswith: 349.0 ms
preg_match_startswith: 20,828.7 ms

وبطبيعة الحال، هذا المعيار قد لا يزال لا تكون منحازة تماما، ولكن وهي تختبر كفاءة الخوارزميات عندما تعطى الإبر مطابقة جزئيا كذلك.

وظيفة substr يمكن العودة false في كثير من الحالات الخاصة، حتى هنا هو بلدي النسخة، التي تتعامل مع هذه القضايا:

function startsWith( $haystack, $needle ){
  return $needle === ''.substr( $haystack, 0, strlen( $needle )); // substr's false => empty string
}

function endsWith( $haystack, $needle ){
  $len = strlen( $needle );
  return $needle === ''.substr( $haystack, -$len, $len ); // ! len=0
}

والاختبارات (true يعني جيد):

var_dump( startsWith('',''));
var_dump( startsWith('1',''));
var_dump(!startsWith('','1'));
var_dump( startsWith('1','1'));
var_dump( startsWith('1234','12'));
var_dump(!startsWith('1234','34'));
var_dump(!startsWith('12','1234'));
var_dump(!startsWith('34','1234'));
var_dump('---');
var_dump( endsWith('',''));
var_dump( endsWith('1',''));
var_dump(!endsWith('','1'));
var_dump( endsWith('1','1'));
var_dump(!endsWith('1234','12'));
var_dump( endsWith('1234','34'));
var_dump(!endsWith('12','1234'));
var_dump(!endsWith('34','1234'));

وأيضا، فإن وظيفة substr_compare أيضا يستحق النظر. http://www.php.net/manual/en/function. SUBSTR-compare.php

وباختصار:

function startsWith($str, $needle){
   return substr($str, 0, strlen($needle)) === $needle;
}

function endsWith($str, $needle){
   $length = strlen($needle);
   return !$length || substr($str, - $length) === $needle;
}

وهذا قد عمل

function startsWith($haystack, $needle) {
     return substr($haystack, 0, strlen($needle)) == $needle;
}

المصدر: https://stackoverflow.com/a/4419658

لماذا لا يلي؟

//How to check if a string begins with another string
$haystack = "valuehaystack";
$needle = "value";
if (strpos($haystack, $needle) === 0){
    echo "Found " . $needle . " at the beginning of " . $haystack . "!";
}

وإخراج:

<اقتباس فقرة>   

وقيمة وجدت في بداية valuehaystack!

ونضع في اعتبارنا، strpos سيعود كاذبة إذا لم يتم العثور على إبرة في كومة قش، وسيعود 0 إذا، وفقط إذا وجد إبرة في الفهرس 0 (AKA البداية).

وهنا endsWith:

$haystack = "valuehaystack";
$needle = "haystack";

//If index of the needle plus the length of the needle is the same length as the entire haystack.
if (strpos($haystack, $needle) + strlen($needle) === strlen($haystack)){
    echo "Found " . $needle . " at the end of " . $haystack . "!";
}

في هذا السيناريو ليست هناك حاجة لstartsWith وظيفة () كما

(strpos($stringToSearch, $doesItStartWithThis) === 0)

وسيعود صحيحة أو خاطئة بدقة.

ويبدو من الغريب انها هذا بسيط مع جميع الوظائف البرية تفشت هنا.

وأود أن تفعل ذلك مثل هذا

     function startWith($haystack,$needle){
              if(substr($haystack,0, strlen($needle))===$needle)
              return true;
        }

  function endWith($haystack,$needle){
              if(substr($haystack, -strlen($needle))===$needle)
              return true;
        }

ومجرد توصية:

function startsWith($haystack,$needle) {
    if($needle==="") return true;
    if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
    return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}

وهذا خط إضافي، مقارنة الحرف الأول من الجمل، ويمكن أن تجعل من كاذبة حالة عودة <م> على الفور ، مما يجعل العديد من المقارنات الخاصة بك على نحو أسرع كثيرا (7X أسرع عندما قمت بقياس). في حالة حقيقية تدفعه عمليا أي سعر في الأداء لأنه سطر واحد لذلك اعتقد انه يستحق منها. (أيضا، من الناحية العملية، عند اختبار العديد من سلاسل للقطعة انطلاق محددة، سوف تفشل معظم المقارنات منذ ذلك الحين في حالة نموذجية كنت تبحث عن شيء ما.)

ويمكنك أيضا استخدام التعابير العادية:

function endsWith($haystack, $needle, $case=true) {
  return preg_match("/.*{$needle}$/" . (($case) ? "" : "i"), $haystack);
}

والعديد من الأجوبة السابقة ستعمل فقط كذلك. ومع ذلك، وهذا هو ربما قصيرة كما يمكنك جعلها، وأنها تفعل ما تشاء. كنت مجرد القول ان كنت ترغب في ذلك إلى "العودة الحقيقي". حتى لقد تضمنت الحلول التي يعود منطقية صواب / خطأ والنصوص صح / خطأ.

// boolean true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 1 : 0;
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 1 : 0;
}


// textual true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 'true' : 'false';
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 'true' : 'false';
}

وبناء على الجواب جيمس بلاك، هنا هو الإصدار endsWith بها:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
}

function endsWith($haystack, $needle, $case=true) {
     return startsWith(strrev($haystack),strrev($needle),$case);

}

ملحوظة: لقد تبادلت جزء إذا-آخر لوظيفة startsWith جيمس بلاك، لأن strncasecmp هو في الواقع نسخة تحسس حالة الأحرف strncmp

وهنا حل فعال لPHP 4. هل يمكن الحصول على نتائج أسرع إذا كان على PHP 5 باستخدام substr_compare بدلا من strcasecmp(substr(...)).

function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strncasecmp($haystack, $beginning, strlen($beginning)) === 0;
    else
        return strncmp($haystack, $beginning, strlen($beginning)) === 0;
}

function stringEndsWith($haystack, $ending, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0;
    else
        return strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false;
}
$ends_with = strrchr($text, '.'); // Ends with dot
$start_with = (0 === strpos($text, '.')); // Starts with dot

ولست متأكدا لماذا هذا من الصعب جدا للناس. SUBSTR يقوم بعمل كبير وغير فعالة كما لا تحتاج للبحث في سلسلة كاملة إذا كان لا يطابق.

وبالإضافة إلى ذلك، لأنني لست فحص القيم صحيح ولكن مقارنة سلاسل لا يكون لديك ما يدعو للقلق بالضرورة عن القضية === صارمة. ومع ذلك، === هو عادة جيدة للوصول الى.

function startsWith($haystack,$needle) {
  substring($haystack,0,strlen($needle)) == $needle) { return true; }
   return false;
}

function endsWith($haystack,$needle) {
  if(substring($haystack,-strlen($needle)) == $needle) { return true; }
   return false;
}

وأو حتى أفضل الأمثل.

function startsWith($haystack,$needle) {
  return substring($haystack,0,strlen($needle)) == $needle);
}

function endsWith($haystack,$needle) {
  return substring($haystack,-strlen($needle)) == $needle);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top