ใช้ str_replace เพื่อให้มันทำหน้าที่เฉพาะในนัดแรกเท่านั้น?


325

ฉันต้องการรุ่นstr_replace()ว่ามีเพียงแทนที่เกิดขึ้นครั้งแรกของใน$search $subjectมีวิธีแก้ไขปัญหานี้อย่างง่ายหรือฉันต้องการโซลูชันแฮ็กหรือไม่?


คุณอาจพบs($subject)->replaceFirst($search)และs($subject)->replaceFirstIgnoreCase($search)เป็นประโยชน์ตามที่พบในห้องสมุดแบบสแตนด์อโลนนี้
caw

คำตอบ:


346

สามารถทำได้ด้วยpreg_replace :

function str_replace_first($from, $to, $content)
{
    $from = '/'.preg_quote($from, '/').'/';

    return preg_replace($from, $to, $content, 1);
}

echo str_replace_first('abc', '123', 'abcdef abcdef abcdef'); 
// outputs '123def abcdef abcdef'

เวทมนตร์อยู่ในพารามิเตอร์ตัวที่สี่ [จำกัด ] จากเอกสาร:

[จำกัด ] - การแทนที่สูงสุดที่เป็นไปได้สำหรับแต่ละรูปแบบในแต่ละสตริงหัวเรื่อง เริ่มต้นที่ -1 (ไม่ จำกัด )


แม้ว่าเห็นคำตอบของ zombatสำหรับวิธีที่มีประสิทธิภาพมากขึ้น (ประมาณ 3-4 เท่าเร็วขึ้น)


39
ข้อเสียของวิธีนี้คือโทษประสิทธิภาพของนิพจน์ทั่วไป
zombat

27
ข้อเสียอีกอย่างคือคุณต้องใช้ preg_quote () ที่ "เข็ม" และหลีกเลี่ยง meta-characters $ และ \ ในการแทนที่
Josh Davis

32
สิ่งนี้ล้มเหลวในฐานะโซลูชันทั่วไปเนื่องจากปัญหาการหลบหนีที่น่ารังเกียจ
Jeremy Kauffman

2
บ่อยครั้งที่นิพจน์ทั่วไปถูกยกเลิกเนื่องจาก 'ประสิทธิภาพ' หากประสิทธิภาพเป็นปัญหาหลักเราจะไม่เขียน PHP! บางสิ่งที่นอกเหนือจาก '/' สามารถใช้เพื่อตัดลวดลายบางทีอาจเป็น '~' ซึ่งจะช่วยหลีกเลี่ยงปัญหาการหลบหนีในระดับหนึ่ง มันขึ้นอยู่กับว่าข้อมูลคืออะไรและมาจากไหน
ThomasRedstone

1
ประสิทธิภาพลดลงจากกัน - ผู้ที่บ่นเกี่ยวกับการหลบหนีปัญหามีอะไรที่เฉพาะเจาะจงในใจนอกเหนือจากข้อบกพร่องที่อาจเกิดขึ้นด้วยpreg_quoteหรือไม่? ตัวอย่างเช่น @ThomasRedstone กังวลว่าตัวคั่น/อาจเป็นอันตรายหากปรากฏขึ้น$fromแต่โชคดีที่ไม่ใช่: มันจะหลบหนีได้อย่างถูกต้องเนื่องจากpreg_quoteพารามิเตอร์ตัวที่สอง (หนึ่งสามารถทดสอบได้อย่างง่ายดาย) ฉันสนใจที่จะรับฟังปัญหาที่เฉพาะเจาะจง (ซึ่งเป็นข้อบกพร่องด้านความปลอดภัย PCRE ร้ายแรงในหนังสือของฉัน)
MvanGeest

611

ไม่มีรุ่น แต่โซลูชันไม่ได้แฮ็กเลย

$pos = strpos($haystack, $needle);
if ($pos !== false) {
    $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}

ค่อนข้างง่ายและประหยัดค่าปรับของนิพจน์ทั่วไป


โบนัส: ถ้าคุณต้องการที่จะเปลี่ยนที่ผ่านมาเกิดขึ้นเพียงแค่ใช้ในสถานที่ของstrrposstrpos


17
สามารถเร็วกว่ามากและจะใช้หน่วยความจำน้อยกว่านิพจน์ทั่วไป ไม่มีความคิดว่าทำไมคนที่จะลงคะแนนลงที่ ...
จอชเดวิส

12
ฉันชอบวิธีการนี้ แต่รหัสมีข้อผิดพลาดพารามิเตอร์สุดท้ายของการเรียก substr_replace ควรเป็น strlen ($ เข็ม) แทน strlen ($ แทนที่) .. โปรดระวังเกี่ยวกับเรื่องนี้ !!
เนลสัน

มันเป็น "แฮ็ค" ในแง่ที่ใช้เวลามากขึ้นในการค้นหาว่าเกิดอะไรขึ้น นอกจากนี้หากเป็นรหัสที่ชัดเจนก็จะไม่ได้รับการกล่าวถึงว่ารหัสมีข้อผิดพลาด หากเป็นไปได้ที่จะทำผิดพลาดในตัวอย่างเล็ก ๆ มันเป็นเรื่องแฮ็คเกินไปแล้ว
Camilo Martin

9
ฉันไม่เห็นด้วยกับ @CamiloMartin เกี่ยวกับจำนวนบรรทัดเทียบกับความเป็นไปได้ของข้อผิดพลาด ในขณะที่substr_replaceเป็นฟังก์ชั่นที่ค่อนข้างใช้งานไม่ได้เนื่องจากพารามิเตอร์ทั้งหมดปัญหาที่แท้จริงคือการจัดการสตริงด้วยตัวเลขนั้นค่อนข้างยุ่งยากบางครั้งคุณต้องระวังให้ผ่านตัวแปร / ออฟเซ็ตที่ถูกต้อง ที่จริงฉันจะไปไกลถึงจะบอกว่ารหัสข้างต้นเป็นตรงไปตรงมามากที่สุดและสำหรับฉันตรรกะวิธีการ
อเล็กซ์

1
วิธีการที่ยอดเยี่ยม ทำงานได้อย่างสมบูรณ์แบบเมื่อแทนที่ค่าตัวแปรที่มีตัวอักษร regex ที่สงวนไว้ (preg_replace จึงมีค่า) ตรงไปตรงมาและสง่างาม
Praesagus

96

แก้ไข: ทั้งสองคำตอบได้รับการปรับปรุงและตอนนี้ถูกต้อง ฉันจะออกคำตอบเนื่องจากการกำหนดเวลาฟังก์ชั่นยังคงมีประโยชน์

คำตอบของ 'zombat' และ 'php มากเกินไป' นั้นไม่ถูกต้อง นี่คือการแก้ไขคำตอบที่ zombat โพสต์ (เนื่องจากฉันไม่มีชื่อเสียงพอที่จะโพสต์ความคิดเห็น):

$pos = strpos($haystack,$needle);
if ($pos !== false) {
    $newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}

สังเกต strlen ($ เข็ม) แทน strlen ($ replace) ตัวอย่างของ Zombat จะทำงานได้อย่างถูกต้องหากเข็มและการเปลี่ยนมีความยาวเท่ากัน

นี่คือฟังก์ชั่นเดียวกันในฟังก์ชั่นที่มีลายเซ็นเดียวกับ str_replace ของ PHP:

function str_replace_first($search, $replace, $subject) {
    $pos = strpos($subject, $search);
    if ($pos !== false) {
        return substr_replace($subject, $replace, $pos, strlen($search));
    }
    return $subject;
}

นี่คือคำตอบที่แก้ไขแล้วของ 'php มากเกินไป':

implode($replace, explode($search, $subject, 2));

สังเกต 2 ตอนท้ายแทน 1 หรือในรูปแบบของฟังก์ชั่น:

function str_replace_first($search, $replace, $subject) {
    return implode($replace, explode($search, $subject, 2));
}

ฉันตั้งเวลาทั้งสองฟังก์ชั่นและฟังก์ชั่นแรกจะเร็วเป็นสองเท่าเมื่อไม่พบที่ตรงกัน พวกเขามีความเร็วเท่ากันเมื่อพบการแข่งขัน


เหตุใดจึงไม่ทำให้เกิดลักษณะเช่นนี้: str_replace_flexible (ผสม $ s, ผสม $ r, int $ ออฟเซ็ต, จำกัด วงเงิน int) ซึ่งฟังก์ชั่นแทนที่การเกิดเหตุการณ์ $ limit เริ่มต้นที่การแข่งขัน $ offset (nth)
Adam Friedman

น่าเสียดายที่นี่ใช้สำหรับการเปลี่ยนตัวพิมพ์เล็กและตัวพิมพ์ใหญ่
andreszs

4
@Andrew stripos()to the rescue :-)
Gras Double

76

ฉันสงสัยว่าอันไหนเร็วที่สุดดังนั้นฉันจึงทดสอบพวกเขาทั้งหมด

ด้านล่างคุณจะพบกับ:

  • รายการที่ครอบคลุมของฟังก์ชั่นทั้งหมดที่มีส่วนร่วมในหน้านี้
  • การทดสอบเกณฑ์มาตรฐานสำหรับแต่ละข้อโต้แย้ง (เวลาดำเนินการเฉลี่ยมากกว่า 10,000 ครั้ง)
  • ลิงก์ไปยังแต่ละคำตอบ (สำหรับรหัสเต็ม)

ฟังก์ชั่นทั้งหมดได้รับการทดสอบด้วยการตั้งค่าเดียวกัน:

$string = 'OOO.OOO.OOO.S';
$search = 'OOO'; 
$replace = 'B';

ฟังก์ชั่นที่จะแทนที่การเกิดขึ้นครั้งแรกของสตริงภายในสตริงเท่านั้น:


ฟังก์ชั่นที่จะแทนที่การเกิดขึ้นครั้งสุดท้ายของสตริงภายในสตริงเท่านั้น:


ขอบคุณสำหรับสิ่งนี้โดยทั่วไปฉันใช้ preg_replace เนื่องจากยืดหยุ่นที่สุดหากต้องการการปรับแต่งในอนาคตในกรณีส่วนใหญ่ 27% ที่ช้ากว่าจะไม่สำคัญ
zzapper

@oLinkWebDevelopment ฉันสนใจที่จะเห็นสคริปต์มาตรฐานของคุณ ฉันคิดว่ามันน่าจะมีประโยชน์
เดฟมอร์ตัน

เหตุผลที่ทำให้substr_replace()ชนะผลลัพธ์นั้นง่าย เพราะมันเป็นฟังก์ชั่นภายใน ฟังก์ชันภายในและสิ่งที่ผู้ใช้กำหนดเองทำสองอย่างนั้นแตกต่างกันในด้านประสิทธิภาพเนื่องจากฟังก์ชันภายในนั้นทำงานในเลเยอร์ที่ต่ำกว่า ดังนั้นทำไมไม่preg_match()? นิพจน์ทั่วไปเกือบจะช้ากว่าฟังก์ชั่นการจัดการสตริงภายในเนื่องจากการค้นหาในสตริงหลายครั้ง
MAChitgarha

1
ผมหวังว่ามาตรฐานใน "ผู้ชนะ" ของคุณ ( substr_replace($string, $replace, 0, strlen($search));) 0ไม่ได้เป็นเพียงการเขียนแบบคงที่ ส่วนหนึ่งของการแก้ปัญหาที่ไม่ใช่ของ regex ก็คือพวกเขาจำเป็นต้อง "ค้นหา" จุดเริ่มต้นก่อนที่จะรู้ว่าจะแทนที่ที่ใด
mickmackusa

55

น่าเสียดายที่ฉันไม่รู้ฟังก์ชัน PHP ที่สามารถทำได้
คุณสามารถหมุนของคุณเองได้อย่างง่ายดายเช่นนี้:

function replace_first($find, $replace, $subject) {
    // stolen from the comments at PHP.net/str_replace
    // Splits $subject into an array of 2 items by $find,
    // and then joins the array with $replace
    return implode($replace, explode($find, $subject, 2));
}

ผมคิดว่านี่เป็นรุ่น golfiestของพวกเขาทั้งหมด - ใช้แทนjoin implode
ติตัส

return implode($replace, explode($find, $subject, $limit+1));สำหรับหมายเลขแทนที่แบบกำหนดเอง
beppe9000

7

ฉันสร้างฟังก์ชั่นเล็ก ๆนี้ที่แทนที่สตริงบนสตริง (คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่) ด้วยขีด จำกัด โดยไม่จำเป็นต้องใช้ Regexp มันใช้งานได้ดี

function str_replace_limit($search, $replace, $string, $limit = 1) {
    $pos = strpos($string, $search);

    if ($pos === false) {
        return $string;
    }

    $searchLen = strlen($search);

    for ($i = 0; $i < $limit; $i++) {
        $string = substr_replace($string, $replace, $pos, $searchLen);

        $pos = strpos($string, $search);

        if ($pos === false) {
            break;
        }
    }

    return $string;
}

ตัวอย่างการใช้งาน:

$search  = 'foo';
$replace = 'bar';
$string  = 'foo wizard makes foo brew for evil foo and jack';
$limit   = 2;

$replaced = str_replace_limit($search, $replace, $string, $limit);

echo $replaced;
// bar wizard makes bar brew for evil foo and jack

แม้ว่าฉันอยากจะทำ===falseแทนที่จะis_bool(ชัดเจนมากขึ้น - ฉันให้นิ้วหัวแม่มือนี้เพียงเพราะหลีกเลี่ยงความบ้าคลั่ง RegExp ! ... และในขณะเดียวกันก็ใช้งานได้ดีและสะอาดหมดจด ...
jave.web

การเลือกpreg_โซลูชันที่ปรับแต่งได้ง่ายไม่ใช่เรื่องบ้าแต่เป็นความชอบส่วนตัว return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1);เป็นเรื่องง่ายที่จะอ่านสำหรับคนที่ไม่กลัว regex ต้องการค้นหาตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่หรือไม่ เพิ่มiหลังจากตัวคั่นรูปแบบสิ้นสุด ต้องการการสนับสนุน unicode / multibyte หรือไม่ เพิ่มuหลังจากตัวคั่นรูปแบบสิ้นสุด ต้องการการสนับสนุนขอบเขตของคำหรือไม่ เพิ่ม\bทั้งสองด้านของสตริงการค้นหาของคุณ หากคุณไม่ต้องการ regex อย่าใช้ regex ม้าสำหรับหลักสูตร แต่ไม่บ้าแน่นอน
mickmackusa

3

วิธีที่ง่ายที่สุดคือการใช้การแสดงออกปกติ

วิธีอื่นคือค้นหาตำแหน่งของสตริงด้วย strpos () จากนั้น substr_replace ()

แต่ฉันจะไปที่ RegExp จริงๆ


"คำใบ้" นี้ค่อนข้างคลุมเครือ / มีค่าต่ำเมื่อเปรียบเทียบกับโพสต์อื่น ๆ ในหน้านี้
mickmackusa

3
function str_replace_once($search, $replace, $subject) {
    $pos = strpos($subject, $search);
    if ($pos === false) {
        return $subject;
    }

    return substr($subject, 0, $pos) . $replace . substr($subject, $pos + strlen($search));
}

คำตอบที่ใช้รหัสเท่านั้นมีมูลค่าต่ำใน StackOverflow เพราะพวกเขาทำงานได้ไม่ดีในการให้ความรู้ / เสริมสร้างศักยภาพให้กับนักวิจัยหลายพันคนในอนาคต
mickmackusa

3

=> รหัสถูกแก้ไขแล้วดังนั้นให้พิจารณาบางความคิดเห็นที่เก่าเกินไป

และขอบคุณทุกคนที่ช่วยฉันปรับปรุงมัน

ข้อผิดพลาดใด ๆ โปรดสื่อสารฉัน ฉันจะแก้ไขมันทันที

ดังนั้นไปสำหรับ:

การแทนที่'o'เป็น'ea' ตัวแรกเช่น:

$s='I love you';
$s=str_replace_first('o','ea',$s);
echo $s;

//output: I leave you

ฟังก์ชั่น:

function str_replace_first($a,$b,$s)
         {
         $w=strpos($s,$a);
         if($w===false)return $s;
         return substr($s,0,$w).$b.substr($s,$w+strlen($a));
         }

ล้มเหลวถ้า $ นี่มีตัวอักษรซ้ำ ๆ เช่น aaa กับ aaaaaaaaa
Cristo

ผมคิดว่าควรจะเป็นไม่ได้substr($where,$b+strlen($this)) substr($where,$b+1)และฉันเดาว่าsubstr_replaceมันเร็วกว่า
ติตัส

แก้ไขรหัสแล้วตอนนี้ใช้ได้แม้กับสายยาว ๆ
PYK

วิธีนี้ไม่ทำงานตามรหัส หลักฐาน: 3v4l.org/cMeZj และเมื่อคุณแก้ไขปัญหาการตั้งชื่อตัวแปรมันจะไม่ทำงานเมื่อไม่พบค่าการค้นหา - มันทำให้สตริงอินพุตเสียหาย หลักฐาน: 3v4l.org/XHtfc
mickmackusa

มันยุติธรรมสำหรับใครบางคนขอให้แก้ไขรหัส? @mickmackusa คุณช่วยตรวจสอบอีกครั้งได้มั้ย
PYK

2
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;

นี่เป็นเช่นเดียวกับคำตอบแรก นอกจากนี้คุณควรจะทำอย่างไรpreg_quoteของ$findก่อนที่จะใช้มันเป็นการแสดงออก
Emil Vikström

นี่คือสิ่งที่ฉันใช้ดังนั้นฉันจึงโหวต คำตอบแรกที่ทำให้เกิดความขัดแย้งกับ Drupal นั้นจะต้องมีการเขียนทับฟังก์ชั่นผู้ช่วย drupal ดังนั้นผมก็เอารหัสที่อยู่ภายในของฟังก์ชั่นและใช้มันในบรรทัดกับส่วนที่เหลือของรหัส ...
แดน Mantyla

คำตอบที่ใช้รหัสนี้เท่านั้นให้คำแนะนำที่ซ้ำซ้อนในหน้า (ไม่พูดถึงมันขาดpreg_quote()คำตอบที่ซ้ำกันปลายนี้สามารถกำจัดได้อย่างปลอดภัยจากหน้าเพราะคำแนะนำที่ให้ไว้โดยก่อนหน้านี้และคำตอบที่ยอมรับ upvoted สูงขึ้น
mickmackusa

2

เพื่อขยายคำตอบ @ renocor ของผมได้เขียนฟังก์ชั่นที่เป็น 100% str_replace()ย้อนกลับเข้ากันได้กับ นั่นคือคุณสามารถแทนที่ทั้งหมดเกิดขึ้นของstr_replace()ด้วยstr_replace_limit()โดยไม่ต้องไปยุ่งอะไรถึงแม้ผู้ใช้อาร์เรย์สำหรับ$search, $replaceและ / หรือ$subjectและ

ฟังก์ชั่นอาจมีอยู่ในตัวเองอย่างสมบูรณ์หากคุณต้องการแทนที่การเรียกใช้ฟังก์ชันด้วย($string===strval(intval(strval($string))))แต่ฉันขอแนะนำเพราะมันvalid_integer()เป็นฟังก์ชั่นที่มีประโยชน์มากกว่าเมื่อจัดการกับจำนวนเต็มที่จัดไว้ให้เป็นสตริง

หมายเหตุ:เมื่อใดก็ตามที่เป็นไปได้str_replace_limit()จะใช้str_replace()แทนดังนั้นการโทรทั้งหมดstr_replace()สามารถเปลี่ยนได้str_replace_limit()โดยไม่ต้องกังวลกับประสิทธิภาพ

การใช้

<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 แทนที่ - bbcbbc

$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

1 รายการทดแทน - bbcabc

$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 แทนที่ - bbcbbc

ฟังก์ชัน

<?php

/**
 * Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
 * are also supported.
 * @param mixed $string
 * @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not 
 */
function valid_integer($string){
    // 1. Cast as string (in case integer is provided)
    // 1. Convert the string to an integer and back to a string
    // 2. Check if identical (note: 'identical', NOT just 'equal')
    // Note: TRUE, FALSE, and NULL $string values all return FALSE
    $string = strval($string);
    return ($string===strval(intval($string)));
}

/**
 * Replace $limit occurences of the search string with the replacement string
 * @param mixed $search The value being searched for, otherwise known as the needle. An
 * array may be used to designate multiple needles.
 * @param mixed $replace The replacement value that replaces found search values. An
 * array may be used to designate multiple replacements.
 * @param mixed $subject The string or array being searched and replaced on, otherwise
 * known as the haystack. If subject is an array, then the search and replace is
 * performed with every entry of subject, and the return value is an array as well. 
 * @param string $count If passed, this will be set to the number of replacements
 * performed.
 * @param int $limit The maximum possible replacements for each pattern in each subject
 * string. Defaults to -1 (no limit).
 * @return string This function returns a string with the replaced values.
 */
function str_replace_limit(
        $search,
        $replace,
        $subject,
        &$count,
        $limit = -1
    ){

    // Set some defaults
    $count = 0;

    // Invalid $limit provided. Throw a warning.
    if(!valid_integer($limit)){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
                'integer', E_USER_WARNING);
        return $subject;
    }

    // Invalid $limit provided. Throw a warning.
    if($limit<-1){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_WARNING);
        return $subject;
    }

    // No replacements necessary. Throw a notice as this was most likely not the intended
    // use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
    // worked around by simply checking to see if $limit===0, and if it does, skip the
    // function call (and set $count to 0, if applicable).
    if($limit===0){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_NOTICE);
        return $subject;
    }

    // Use str_replace() whenever possible (for performance reasons)
    if($limit===-1){
        return str_replace($search, $replace, $subject, $count);
    }

    if(is_array($subject)){

        // Loop through $subject values and call this function for each one.
        foreach($subject as $key => $this_subject){

            // Skip values that are arrays (to match str_replace()).
            if(!is_array($this_subject)){

                // Call this function again for
                $this_function = __FUNCTION__;
                $subject[$key] = $this_function(
                        $search,
                        $replace,
                        $this_subject,
                        $this_count,
                        $limit
                );

                // Adjust $count
                $count += $this_count;

                // Adjust $limit, if not -1
                if($limit!=-1){
                    $limit -= $this_count;
                }

                // Reached $limit, return $subject
                if($limit===0){
                    return $subject;
                }

            }

        }

        return $subject;

    } elseif(is_array($search)){
        // Only treat $replace as an array if $search is also an array (to match str_replace())

        // Clear keys of $search (to match str_replace()).
        $search = array_values($search);

        // Clear keys of $replace, if applicable (to match str_replace()).
        if(is_array($replace)){
            $replace = array_values($replace);
        }

        // Loop through $search array.
        foreach($search as $key => $this_search){

            // Don't support multi-dimensional arrays (to match str_replace()).
            $this_search = strval($this_search);

            // If $replace is an array, use the value of $replace[$key] as the replacement. If
            // $replace[$key] doesn't exist, just an empty string (to match str_replace()).
            if(is_array($replace)){
                if(array_key_exists($key, $replace)){
                    $this_replace = strval($replace[$key]);
                } else {
                    $this_replace = '';
                }
            } else {
                $this_replace = strval($replace);
            }

            // Call this function again for
            $this_function = __FUNCTION__;
            $subject = $this_function(
                    $this_search,
                    $this_replace,
                    $subject,
                    $this_count,
                    $limit
            );

            // Adjust $count
            $count += $this_count;

            // Adjust $limit, if not -1
            if($limit!=-1){
                $limit -= $this_count;
            }

            // Reached $limit, return $subject
            if($limit===0){
                return $subject;
            }

        }

        return $subject;

    } else {
        $search = strval($search);
        $replace = strval($replace);

        // Get position of first $search
        $pos = strpos($subject, $search);

        // Return $subject if $search cannot be found
        if($pos===false){
            return $subject;
        }

        // Get length of $search, to make proper replacement later on
        $search_len = strlen($search);

        // Loop until $search can no longer be found, or $limit is reached
        for($i=0;(($i<$limit)||($limit===-1));$i++){

            // Replace 
            $subject = substr_replace($subject, $replace, $pos, $search_len);

            // Increase $count
            $count++;

            // Get location of next $search
            $pos = strpos($subject, $search);

            // Break out of loop if $needle
            if($pos===false){
                break;
            }

        }

        // Return new $subject
        return $subject;

    }

}

4
ค่อนข้างป่องถ้าคุณถามฉัน สิ่งที่ฉันเกลียดที่สุดในโซลูชันนี้คือการจัดการข้อผิดพลาด มันแบ่งสคริปต์ถ้าคุณผ่านค่าที่ไม่ถูกต้อง คุณคิดว่ามันดูเป็นมืออาชีพ แต่ไม่ใช่แทนที่จะเป็นข้อผิดพลาดให้แจ้งเตือนหรือเตือนแทน ดีกว่าคือข้ามพล่ามกลับเท็จแทนหรือเป็นโมฆะและไม่เคยใช้ backtrace ในฟังก์ชั่นเช่นนี้ ทางออกที่ดีที่สุดคือโปรแกรมเมอร์สามารถตัดสินใจว่าจะทำอย่างไรเมื่อผลลัพธ์ผิด / ไม่คาดคิด
Codebeat

@Erwinus ใช้นี้E_USER_WARNINGตลอดซึ่งเป็นคำเตือน , ไม่ข้อผิดพลาด backtrace มีประโยชน์อย่างยิ่งในการค้นหาว่ารหัสใดที่ส่งข้อมูลที่ไม่ถูกต้องไปยังฟังก์ชันในตอนแรก (ซึ่งจำเป็นอย่างยิ่งในการติดตามข้อบกพร่องในการผลิต) สำหรับการกลับมา$subjectแทนที่จะfalse/ nullหรือโยนข้อผิดพลาดนั่นเป็นเพียงตัวเลือกส่วนบุคคลสำหรับกรณีการใช้งานของฉัน เพื่อให้ตรงกับstr_replace()การใช้งานของการใช้ข้อผิดพลาดร้ายแรงที่จับได้จะเป็นทางออกที่ดีที่สุด (เช่นstr_replace()เมื่อมีการปิดสำหรับสองอาร์กิวเมนต์แรก)
0b10011

อ๊ะไม่ได้สังเกตเกี่ยวกับ E_USER_WARNING ที่คุณใช้ขออภัยด้วย ปัญหาในการส่งคืนหัวเรื่องคือคุณไม่สามารถมองเห็นสิ่งผิดปกตินอกฟังก์ชั่น ที่กล่าวว่าฟังก์ชั่นอาจมีขนาดครึ่งหนึ่งถ้าคุณทำอย่างชาญฉลาด (เป็นไปได้) ประการที่สองความคิดเห็นดีเมื่ออธิบายบางสิ่งที่ซับซ้อน แต่ไม่มีประโยชน์สำหรับสิ่งที่เรียบง่ายเช่นเพิ่มมูลค่า โดยรวมแล้วฉันคิดว่ามันไม่จำเป็นมาก นอกจากนี้การใช้คำเตือนในสภาพแวดล้อมการผลิตอาจเป็นปัญหาด้านความปลอดภัยเมื่อคุณใช้รหัสนี้บนเซิร์ฟเวอร์ที่ไม่ระงับข้อความขณะใช้งานตามค่าเริ่มต้น (บันทึก)
Codebeat

@Erwinus ฉันได้รับความคิดเห็นอย่างล้นหลามเพราะบางคนไม่เข้าใจภาษารวมถึงคนอื่น ๆ และผู้ที่เข้าใจจะสามารถลบความคิดเห็นได้เสมอ หากคุณทราบวิธีที่ดีกว่าในการรับผลลัพธ์สุดท้ายที่เหมือนกันสำหรับเคสขอบทั้งหมดโดยทั้งหมดให้แก้ไขคำตอบ และหากสภาพแวดล้อมการผลิตของคุณไม่ระงับข้อความแสดงข้อผิดพลาดแสดงว่าคุณมีปัญหาใหญ่กว่าฟังก์ชั่นนี้)
0b10011

TL; DR ตัวอย่างนี้ป่องมากจนฉันนึกภาพออกไม่ได้ว่าเลือกฟังก์ชัน regex (ฉันเกลียดการเลื่อน) preg_replace()หากคุณต้องการที่จะนับทดแทนที่ทำมีพารามิเตอร์สำหรับการที่ใน นอกจากนี้preg_replace()/ regex มีการจัดการขอบเขตคำ (ถ้าเป็นที่ต้องการ) - สิ่งที่ฟังก์ชั่นที่ไม่ใช่ regex จะไม่ให้อย่างหรูหรา
mickmackusa

2

จากผลการทดสอบของฉันฉันต้องการลงคะแนน regular_express ที่จัดทำโดย karim79 (ฉันมีชื่อเสียงไม่พอที่จะลงคะแนนตอนนี้!)

โซลูชันจาก zombat ใช้การเรียกใช้ฟังก์ชันมากเกินไปฉันจะทำให้รหัสง่ายขึ้น ฉันใช้ PHP 5.4 เพื่อรันทั้งสองวิธี 100,000 ครั้งและนี่คือผลลัพธ์:

$str = 'Hello abc, have a nice day abc! abc!';
$pos = strpos($str, 'abc');
$str = substr_replace($str, '123', $pos, 3);

==> 1.85 วินาที

$str = 'Hello abc, have a nice day abc! abc!';
$str = preg_replace('/abc/', '123', $str, 1);

==> 1.35 วินาที

อย่างที่เห็น. ประสิทธิภาพของ preg_replace นั้นไม่เลวร้ายอย่างที่หลายคนคิด ดังนั้นฉันขอแนะนำทางออกที่ดีงามหากการแสดงปกติของคุณไม่ซับซ้อน


ตัวอย่างแรกของคุณเป็นการเปรียบเทียบที่ไม่เป็นธรรมเพราะไม่สามารถใช้งานได้อย่างถูกต้อง คุณยังไม่ได้ตรวจสอบ$posสำหรับfalseดังนั้นเมื่อเข็มไม่ได้อยู่ในกองหญ้าก็จะเกิดความเสียหายเอาท์พุท
mickmackusa

ขอบคุณ @mickmackusa คุณพูดถูก แต่ไม่ thats จุด. ฉันว่ารหัสนี้ง่ายขึ้นเพียงเพื่อเปรียบเทียบประสิทธิภาพของการใช้งาน
ฮันเตอร์วู

นั่นคือประเด็นของฉัน คุณต้องไม่ทำการเปรียบเทียบเปรียบเทียบที่ไม่ได้ทำกระบวนการเดียวกัน การเปรียบเทียบแอปเปิ้ลกับส้มครึ่งไม่มีประโยชน์ การใช้วิธีการที่ไม่ใช่ regex อย่างสมบูรณ์จะทำให้ความแตกต่างของความเร็วลึกซึ้งยิ่งขึ้น
mickmackusa

ขอบคุณอีกครั้ง แต่สิ่งที่ฉันต้องการคือการค้นหาการใช้งานที่ดีกว่าไม่ใช่เพื่อสร้างความแตกต่างอย่างลึกซึ้ง
ฮันเตอร์วู

2

เพื่อขยายคำตอบของ zombat (ซึ่งฉันเชื่อว่าเป็นคำตอบที่ดีที่สุด) ฉันได้สร้างฟังก์ชันแบบเรียกซ้ำที่ใช้$limitพารามิเตอร์เพื่อระบุจำนวนเหตุการณ์ที่คุณต้องการแทนที่

function str_replace_limit($haystack, $needle, $replace, $limit, $start_pos = 0) {
    if ($limit <= 0) {
        return $haystack;
    } else {
        $pos = strpos($haystack,$needle,$start_pos);
        if ($pos !== false) {
            $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
            return str_replace_limit($newstring, $needle, $replace, $limit-1, $pos+strlen($replace));
        } else {
            return $haystack;
        }
    }
}

หมายเหตุไม่มีการตรวจสอบคุณภาพดังนั้นถ้ามันอยู่นอกเหนือความยาวสตริงฟังก์ชั่นนี้จะสร้าง:$start_pos Warning: strpos(): Offset not contained in string...ฟังก์ชั่นนี้ไม่สามารถทำการทดแทนเมื่อ$start_posเกินความยาว หลักฐานการล้มเหลว: 3v4l.org/qGuVIR ... ฟังก์ชั่นของคุณสามารถรวมreturn $haystackเงื่อนไขและหลีกเลี่ยงการประกาศตัวแปรแบบใช้ครั้งเดียวเช่นนี้: 3v4l.org/Kdmqp อย่างไรก็ตามอย่างที่ฉันพูดในความคิดเห็นที่อื่นในหน้านี้ฉันต้องการ ใช้สะอาดมากตรงที่ไม่ใช่ recursive preg_replace()โทร
mickmackusa

ใช่เพื่อให้คุณสามารถเพิ่มelsestatment บรรทัดนี้$start_pos > strlen($haystack) ? $start_pos = strlen($haystack) : '';
Manojkiran.A

2

สำหรับสตริง

$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';

//replace ONLY FIRST occurance of "OOO" with "B"
    $string = substr_replace($string,$replace,0,strlen($search));
    //$string => B.OOO.OOO.S

//replace ONLY LAST occurance of "OOOO" with "B"
    $string = substr_replace($string,$replace,strrpos($string,$search),strlen($search)) 
    //$string => OOO.OOO.B.S

    //replace ONLY LAST occurance of "OOOO" with "B"
    $string = strrev(implode(strrev($replace),explode(strrev($search),strrev($string),2)))
    //$string => OOO.OOO.B.S

สำหรับตัวละครตัวเดียว

$string[strpos($string,$search)] = $replace;


//EXAMPLE

$string = 'O.O.O.O.S';
$search = 'O';
$replace = 'B';

//replace ONLY FIRST occurance of "O" with "B" 
    $string[strpos($string,$search)] = $replace;  
    //$string => B.O.O.O.S

//replace ONLY LAST occurance of "O" with "B" 
    $string[strrpos($string,$search)] = $replace; 
    // $string => B.O.O.B.S

ตัวอย่างย่อย substr_replace () แรกล้มเหลวเมื่อสตริงการค้นหาไม่ตรงกับค่า 0 ของสตริงอินพุต หลักฐานการล้มเหลว: 3v4l.org/oIbRvและทั้งสองsubstr_replace()เทคนิคสร้างความเสียหายให้กับสตริงอินพุตเมื่อไม่มีค่าการค้นหา หลักฐานการล้มเหลว: 3v4l.org/HmEml (และเทคนิคสุดท้ายที่มีการrevเรียกทั้งหมดนั้นซับซ้อนมาก / ยากต่อสายตา)
mickmackusa

2

การเติมเต็มสิ่งที่ผู้คนพูดไว้โปรดจำไว้ว่าสตริงทั้งหมดเป็นอาร์เรย์:

$string = "Lorem ipsum lá lá lá";

$string[0] = "B";

echo $string;

"Borem ipsum lálálá"


3
ถ้ามันมีอักขระหลายไบต์ ... และเทคนิคของคุณล้มเหลว áวิธีโชคร้ายที่คุณนำเสนอสายป้อนตัวอย่างที่มี การสาธิตความล้มเหลว
mickmackusa

คุณสามารถตรวจสอบว่าคุณstringเป็นสตริงหลายไบต์โดยใช้mb_strlen($subject) != strlen($subject)
RousseauAlexandre

โพสต์นี้ไม่ได้พยายามตอบคำถามที่ถูกถาม
mickmackusa

2
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);

การใช้ substr_replace เราสามารถแทนที่การเกิดขึ้นของอักขระตัวแรกในสตริงเท่านั้น ในฐานะ & ซ้ำหลายครั้ง แต่ในตำแหน่งแรกเราต้องแทนที่ & ด้วย?


1

ฟังก์ชั่นนี้ได้แรงบันดาลใจอย่างมากจากคำตอบของ @renocor มันทำให้ฟังก์ชั่นหลายไบต์ปลอดภัย

function str_replace_limit($search, $replace, $string, $limit)
{
    $i = 0;
    $searchLength = mb_strlen($search);

    while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
    {
        $string = mb_substr_replace($string, $replace, $pos, $searchLength);
        $i += 1;
    }

    return $string;
}

function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
{
    $string = (array)$string;
    $encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
    $length = is_null($length) ? mb_strlen($string) - $start : $length;

    $string = array_map(function($str) use ($replacement, $start, $length, $encoding){

        $begin = mb_substr($str, 0, $start, $encoding);
        $end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);

        return $begin . $replacement . $end;

    }, $string);

    return ( count($string) === 1 ) ? $string[0] : $string;
}

0

คุณสามารถใช้สิ่งนี้:

function str_replace_once($str_pattern, $str_replacement, $string){ 

        if (strpos($string, $str_pattern) !== false){ 
            $occurrence = strpos($string, $str_pattern); 
            return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern)); 
        } 

        return $string; 
    } 

พบตัวอย่างนี้จาก php.net

การใช้งาน:

$string = "Thiz iz an examplz";
var_dump(str_replace_once('z','Z', $string)); 

เอาท์พุท:

ThiZ iz an examplz

นี่อาจลดประสิทธิภาพลงเล็กน้อย แต่เป็นวิธีที่ง่ายที่สุด


ถ้านั่นคือเอาต์พุตมากกว่าจุดคืออะไร? ไม่ควรแทนที่ "z" ตัวพิมพ์เล็กตัวแรกด้วยตัวพิมพ์ใหญ่ "Z" ใช่หรือไม่ แทนที่จะแทนที่พวกเขาทั้งหมดเหรอ? ฉันคิดว่านั่นคือสิ่งที่เรากำลังพูดถึงที่นี่ ...
หมุน

ฉันไม่ดีมันจะแทนที่เหตุการณ์ที่เกิดขึ้นครั้งแรกเท่านั้น แก้ไข
happyhardik

คำแนะนำเดียวกันนี้เสนอโดย Bas เกือบ 3 ปีก่อนหน้านี้ (และไม่มีการโทรมากเกินไปstrpos()) ลดลงเพราะไม่ได้เพิ่มคุณค่าใหม่ให้กับหน้าเว็บ
mickmackusa

0

หากสตริงของคุณไม่มีอักขระหลายไบต์และหากคุณต้องการแทนที่อักขระตัวเดียวคุณสามารถใช้ strpos

นี่คือฟังก์ชันที่จัดการข้อผิดพลาด

/**
 * Replace the first occurence of given string
 *
 * @param  string $search  a char to search in `$subject`
 * @param  string $replace a char to replace in `$subject`
 * @param  string $subject
 * @return string
 *
 * @throws InvalidArgumentException if `$search` or `$replace` are invalid or if `$subject` is a multibytes string
 */
function str_replace_first(string $search , string $replace , string $subject) : string {
    // check params
    if(strlen($replace) != 1 || strlen($search) != 1) {
        throw new InvalidArgumentException('$search & $replace must be char');
    }elseif(mb_strlen($subject) != strlen($subject)){
        throw new InvalidArgumentException('$subject is an multibytes string');
    }
    // search 
    $pos = strpos($subject, $search);
    if($pos === false) {
        // not found
        return $subject;
    }

    // replace
    $subject[$replace] = $subject;

    return $subject;
}

0

สำหรับโซลูชั่นวน

<?php
echo replaceFirstMatchedChar("&", "?", "/property/details&id=202&test=123#tab-6");

function replaceFirstMatchedChar($searchChar, $replaceChar, $str)
{
    for ($i = 0; $i < strlen($str); $i++) {

        if ($str[$i] == $searchChar) {
            $str[$i] = $replaceChar;
            break;
        }
    }
    return $str;
}

-1

นี่คือคลาสธรรมดาที่ฉันสร้างขึ้นเพื่อรวมฟังก์ชันstr_replace () ที่ปรับเปลี่ยนเล็กน้อยของเรา

php :: str_rreplace () ฟังก์ชั่นของเรายังช่วยให้คุณดำเนินการย้อนกลับ str_replace จำกัด () ซึ่งจะมีประโยชน์มากเมื่อพยายามที่จะแทนที่เพียงอินสแตนซ์ X สุดท้ายของสตริง

ตัวอย่างเหล่านี้ใช้งานทั้งpreg_replace ()

<?php
class php {

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_replace($find, $replace, $subject, $replacement_limit = -1) {
        $find_pattern = str_replace('/', '\/', $find);
        return preg_replace('/' . $find_pattern . '/', $replace, $subject, $replacement_limit);
    }

    /**
    * str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
    *
    * @param string   $find
    * @param string   $replace
    * @param string   $subject
    * @param int      $replacement_limit | -1 to replace all references
    *
    * @return string
    */
    public static function str_rreplace($find, $replace, $subject, $replacement_limit = -1) {
        return strrev( self::str_replace(strrev($find), strrev($replace), strrev($subject), $replacement_limit) );
    }
}

โพสต์ของคุณไม่ได้เพิ่มคุณค่าให้กับสิ่งนี้ผ่านหน้าอิ่มตัว โซลูชัน regex ของคุณล้มเหลวในหลาย ๆ กรณีเนื่องจากคุณใช้เครื่องมือที่ไม่ถูกต้องเพื่อหนีอักขระในสตริงเข็ม หลักฐานการล้มเหลว: 3v4l.org/dTdYK คำตอบที่ได้รับการยอมรับอย่างสูงและได้รับการยอมรับอย่างมากตั้งแต่ปี 2009แสดงให้เห็นถึงการดำเนินการที่เหมาะสมของเทคนิคนี้ วิธีที่สองของคุณไม่ตอบคำถามที่ถามและมีให้โดย oLinkWebDevelopment แล้ว
mickmackusa

-1
$str = "Hello there folks!"
$str_ex = explode("there, $str, 2);   //explodes $string just twice
                                      //outputs: array ("Hello ", " folks")
$str_final = implode("", $str_ex);    // glues above array together
                                      // outputs: str("Hello  folks")

มีที่ว่างเพิ่มอีกหนึ่งช่อง แต่มันก็ไม่สำคัญเหมือนกันกับสคริปต์แบคกราวน์ในกรณีของฉัน


เทคนิคนี้จัดทำโดย toomuchphp ย้อนกลับไปในปี 2009 ! ฉันลงคะแนนเพราะโพสต์ของคุณไม่เพิ่มคุณค่าใหม่ให้กับนักวิจัย โปรดตรวจสอบให้แน่ใจก่อนโพสต์คำตอบว่าโซลูชันของคุณไม่ซ้ำใครในหน้าและเพิ่มมูลค่าให้กับหน้า
mickmackusa

-3

นี่เป็นคำตอบแรกของฉันที่นี่ฉันหวังว่าจะทำอย่างถูกต้อง ทำไมไม่ใช้อาร์กิวเมนต์ที่สี่ของฟังก์ชัน str_replace สำหรับปัญหานี้

mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )

นับ: ถ้าผ่านจะถูกกำหนดเป็นจำนวนการเปลี่ยนที่ทำ


แก้ไข: คำตอบนี้ไม่ถูกต้องเพราะพารามิเตอร์ที่ 4 ของstr_replaceเป็นตัวแปรที่ได้รับมอบหมายจำนวนของการแทนที่ทำ นี้ไม่สอดคล้องกับpreg_replaceซึ่งมีพารามิเตอร์ที่ 4 และพารามิเตอร์ที่$limit 5&$count


อาร์กิวเมนต์ที่สี่จะถูกตั้งค่าโดย str_replace () ตามจำนวนการแทนที่ที่สร้างขึ้น นั่นคือเหตุผลที่คุณได้รับข้อผิดพลาดเมื่อคุณส่งจำนวนเต็มไม่ใช่ตัวแปรไป
arminrosu

-6

ง่ายต่อการค้นหาโซลูชันเพื่อแทนที่อินสแตนซ์คู่แรกหรือคู่แรกของอินสแตนซ์ (โดยให้ค่าการนับ) มีวิธีแก้ไขไม่มากที่จะแทนที่อินสแตนซ์สุดท้ายหรือคู่สุดท้ายของอินสแตนซ์

บางทีสิ่งที่ต้องการ str_replace ($ find, $ replace, $ subject, -3) ควรแทนที่สามอินสแตนซ์ล่าสุด

อย่างไรก็ตามเพียงแค่ข้อเสนอแนะ


4
ทำไมต้องตอบคำถามพร้อมคำแนะนำเมื่อคำตอบนั้นได้รับการยอมรับเมื่อสองปีก่อน!
mbinette

นอกจากนี้ -3 จะไม่ทำงานเป็นพารามิเตอร์เนื่องจากพารามิเตอร์ที่ 4 คือเอาต์พุตและไม่ใช่พารามิเตอร์อินพุต มันจะดีกว่าถ้าคุณทดสอบสิ่งที่คุณเสนอแทนที่จะโพสต์รหัสที่ขัดข้อง
Ghostwriter78

ใช่นี่เป็นสิ่งที่ผิด แต่ทำไมฉันถึงเกิดข้อผิดพลาดที่หน้าจอว่างเมื่อฉันลอง ฉันทำข้อผิดพลาดตามปกติ (E_ALL); ini_set ("display_errors", 1); ข้อผิดพลาดหน้าจอยังว่างเปล่า
ดั๊กแคสสิดี้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.