มีกลไกในการวนรอบ x ครั้งใน ES6 (ECMAScript 6) โดยไม่มีตัวแปรที่ไม่แน่นอนหรือไม่?


157

วิธีทั่วไปในการวนรอบxเวลาใน JavaScript คือ:

for (var i = 0; i < x; i++)
  doStuff(i);

แต่ฉันไม่ต้องการใช้++โอเปอเรเตอร์หรือมีตัวแปรที่ไม่แน่นอนใด ๆ มีวิธีใดใน ES6 ที่จะวนซ้ำxอีกทางหนึ่ง ฉันรักกลไกของรูบี้:

x.times do |i|
  do_stuff(i)
end

มีอะไรที่คล้ายกันใน JavaScript / ES6? ฉันสามารถโกงและสร้างเครื่องกำเนิดของตัวเองได้:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

แน่นอนฉันยังใช้i++อยู่ อย่างน้อยก็ออกนอกสายตา :) แต่ฉันหวังว่าจะมีกลไกที่ดีกว่าใน ES6


3
เหตุใดตัวแปรควบคุมลูปที่ไม่แน่นอนจึงเป็นปัญหา เป็นเพียงหลักการหรือไม่?
doldt

1
@doldt - ฉันพยายามสอน JavaScript แต่ฉันกำลังทดลองกับการชะลอแนวคิดของตัวแปรที่ไม่แน่นอนจนกระทั่งในภายหลัง
ที่

5
เรากำลังจะปิดหัวข้อที่นี่จริง ๆ แต่คุณแน่ใจหรือไม่ว่าการย้ายไปยังเครื่องกำเนิด ES6 (หรือแนวคิดใหม่ระดับสูงอื่น ๆ ) เป็นความคิดที่ดีก่อนที่พวกเขาจะเรียนรู้เกี่ยวกับตัวแปรที่ผันแปรได้หรือไม่ :)
doldt

5
@doldt - บางทีฉันกำลังทดลอง ใช้แนวทางภาษาที่ใช้งานได้กับ JavaScript
ที่

ใช้คำสั่ง let เพื่อประกาศตัวแปรนั้นในลูป ขอบเขตของมันจบลงด้วยการวนซ้ำ
ncmathsadist

คำตอบ:


156

ตกลง!

โค้ดด้านล่างนี้เขียนโดยใช้ไวยากรณ์ ES6 แต่สามารถเขียนได้อย่างง่ายดายใน ES5 หรือแม้แต่น้อย ES6 ไม่ใช่ข้อกำหนดในการสร้าง "กลไกการวนรอบ x ครั้ง"


หากคุณไม่ต้องการตัววนซ้ำในการติดต่อกลับนี่เป็นการใช้งานที่ง่ายที่สุด

const times = x => f => {
  if (x > 0) {
    f()
    times (x - 1) (f)
  }
}

// use it
times (3) (() => console.log('hi'))

// or define intermediate functions for reuse
let twice = times (2)

// twice the power !
twice (() => console.log('double vision'))

หากคุณต้องการตัววนซ้ำคุณสามารถใช้ฟังก์ชัน inner ที่มีชื่อพร้อมพารามิเตอร์ตัวนับเพื่อทำซ้ำให้กับคุณ

const times = n => f => {
  let iter = i => {
    if (i === n) return
    f (i)
    iter (i + 1)
  }
  return iter (0)
}

times (3) (i => console.log(i, 'hi'))


หยุดอ่านที่นี่ถ้าคุณไม่ชอบการเรียนรู้สิ่งต่าง ๆ มากขึ้น ...

แต่สิ่งที่ควรรู้สึกเกี่ยวกับสิ่งเหล่านั้น ...

  • ifคำสั่งสาขาเดียวน่าเกลียด - เกิดอะไรขึ้นกับสาขาอื่น
  • มีหลายข้อความ / นิพจน์ในส่วนของฟังก์ชั่น - มีความกังวลเกี่ยวกับกระบวนการหรือไม่?
  • กลับมาโดยปริยายundefined- บ่งบอกถึงการไม่บริสุทธิ์ฟังก์ชั่นผลข้างเคียง

"ไม่มีทางที่ดีกว่านี้อีกเหรอ?"

นั่นคือ ก่อนอื่นมาทบทวนการใช้งานครั้งแรกของเรา

// times :: Int -> (void -> void) -> void
const times = x => f => {
  if (x > 0) {
    f()               // has to be side-effecting function
    times (x - 1) (f)
  }
}

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

ถ้าเราเริ่มต้นด้วยขั้นตอนการทำซ้ำฟังก์ชั่นที่ดีกว่า บางทีสิ่งที่ทำให้การใช้อินพุทและเอาท์พุทดีขึ้น

การทำซ้ำฟังก์ชั่นทั่วไป

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// power :: Int -> Int -> Int
const power = base => exp => {
  // repeat <exp> times, <base> * <x>, starting with 1
  return repeat (exp) (x => base * x) (1)
}

console.log(power (2) (8))
// => 256

ด้านบนเรากำหนดrepeatฟังก์ชันทั่วไปซึ่งรับอินพุตเพิ่มเติมซึ่งใช้เพื่อเริ่มต้นแอปพลิเคชันซ้ำของฟังก์ชันเดียว

// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)

// is the same as ...
var result = f(f(f(x)))

ดำเนินการtimesด้วยrepeat

อย่างนี้ง่ายตอนนี้; งานเกือบทั้งหมดได้ทำไปแล้ว

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// times :: Int -> (Int -> Int) -> Int 
const times = n=> f=>
  repeat (n) (i => (f(i), i + 1)) (0)

// use it
times (3) (i => console.log(i, 'hi'))

เนื่องจากฟังก์ชั่นของเราใช้iเป็นอินพุตและส่งคืนi + 1สิ่งนี้จะทำงานได้อย่างมีประสิทธิภาพเป็นตัววนซ้ำของเราที่เราส่งไปfแต่ละครั้ง

เราได้แก้ไขรายการหัวข้อย่อยของปัญหาด้วย

  • ไม่มีifงบสาขาเดียวที่น่าเกลียดอีกต่อไป
  • เนื้อความแบบนิพจน์เดียวบ่งบอกถึงความกังวลที่แยกจากกันอย่างชัดเจน
  • ไม่มีประโยชน์มากขึ้นกลับโดยปริยาย undefined

ตัวดำเนินการจุลภาคของจุลภาค,

ในกรณีที่คุณมีปัญหาในการดูว่าตัวอย่างล่าสุดทำงานอย่างไรนั้นขึ้นอยู่กับการรับรู้ของคุณในหนึ่งในแกนต่อสู้ที่เก่าแก่ที่สุดของ JavaScript ตัวดำเนินการคอมม่า - ในระยะสั้นจะประเมินนิพจน์จากซ้ายไปขวาและส่งคืนค่าของนิพจน์ที่ประเมินล่าสุด

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

ในตัวอย่างข้างต้นเราใช้

(i => (f(i), i + 1))

ซึ่งเป็นเพียงวิธีการเขียนรวบรัด

(i => { f(i); return i + 1 })

การเพิ่มประสิทธิภาพการโทรหาง

ณ จุดนี้มันจะไม่มีความรับผิดชอบสำหรับฉันที่จะแนะนำพวกเขาเนื่องจากไม่มีJavaScript VM VM ที่ฉันสามารถคิดว่ารองรับการกำจัดหางอย่างเหมาะสม - Babel เคยใช้ transpile แต่ก็อยู่ในสภาพ "เสียแล้วจะนำกลับมาใช้ใหม่" "สถานะดีเกินปี

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

ดังนั้นเราควรกลับไปใช้การดำเนินการrepeatเพื่อให้ปลอดภัย

รหัสด้านล่างนี้ใช้ตัวแปรที่เปลี่ยนแปลงได้nและxโปรดทราบว่าการกลายพันธุ์ทั้งหมดจะถูกแปลเป็นภาษาท้องถิ่นไปยังrepeatฟังก์ชั่น - ไม่มีการเปลี่ยนแปลงสถานะ (การกลายพันธุ์) ที่สามารถมองเห็นได้จากนอกฟังก์ชั่น

// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
  {
    let m = 0, acc = x
    while (m < n)
      (m = m + 1, acc = f (acc))
    return acc
  }

// inc :: Int -> Int
const inc = x =>
  x + 1

console.log (repeat (1e8) (inc) (0))
// 100000000

สิ่งนี้จะมีคุณหลายคนพูดว่า "แต่นั่นไม่สามารถใช้งานได้!" - ฉันรู้แค่พักผ่อน เราสามารถใช้ Clojure สไตล์loop/ recurอินเตอร์เฟซสำหรับคงพื้นที่การวนลูปโดยใช้การแสดงออกบริสุทธิ์ ; ไม่มีwhileสิ่งนั้น

ที่นี่เราสรุปwhileไปกับloopฟังก์ชั่นของเรา- มันมองหารูปแบบพิเศษrecurเพื่อให้วงวิ่งต่อไป เมื่อไม่recurพบประเภทวนรอบจะเสร็จสิ้นและผลลัพธ์ของการคำนวณจะถูกส่งกลับ

const recur = (...args) =>
  ({ type: recur, args })
  
const loop = f =>
  {
    let acc = f ()
    while (acc.type === recur)
      acc = f (...acc.args)
    return acc
  }

const repeat = $n => f => x =>
  loop ((n = $n, acc = x) =>
    n === 0
      ? acc
      : recur (n - 1, f (acc)))
      
const inc = x =>
  x + 1

const fibonacci = $n =>
  loop ((n = $n, a = 0, b = 1) =>
    n === 0
      ? a
      : recur (n - 1, b, a + b))
      
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100))        // 354224848179262000000


24
ดูเหมือนจะซับซ้อนเกินไป (ฉันสับสนโดยเฉพาะกับg => g(g)(x)) มีประโยชน์จากฟังก์ชันที่มีลำดับสูงกว่าคำสั่งซื้อแรกเช่นในโซลูชันของฉันหรือไม่
Pavlo

1
@naomik: ขอบคุณที่สละเวลาโพสต์ลิงก์ ชื่นชมมาก
Pineda

1
@ AlfonsoPérezฉันขอขอบคุณคำพูด ฉันจะดูว่าฉันสามารถทำงานได้เล็กน้อยในที่ใดที่หนึ่ง ^ _ ^
ขอบคุณ

1
@naomik Farewell TCO ! ฉันเสียใจมาก

10
ดูเหมือนว่าคำตอบนี้ได้รับการยอมรับและได้รับการจัดอันดับอย่างดีเพราะต้องใช้ความพยายามอย่างมาก แต่ฉันไม่คิดว่ามันเป็นคำตอบที่ดี คำตอบที่ถูกต้องสำหรับคำถามคือ "ไม่" จะเป็นประโยชน์ในการระบุวิธีแก้ปัญหาตามที่คุณทำ แต่หลังจากนั้นคุณระบุว่ามีวิธีที่ดีกว่า ทำไมคุณไม่ลองใส่คำตอบนั้นและลบคำตอบที่แย่กว่านั้นออก เหตุใดคุณจึงอธิบายผู้ประกอบเครื่องหมายจุลภาค ทำไมคุณถึงนำ Clojure ขึ้นมา? ทำไมโดยทั่วไปแทนเจนต์จำนวนมากสำหรับคำถามที่มีคำตอบ 2 ตัวอักษร? คำถามง่ายๆไม่ได้เป็นเพียงแพลตฟอร์มสำหรับผู้ใช้งานนำเสนอเกี่ยวกับข้อเท็จจริงการเขียนโปรแกรมที่ประณีต
Timofey 'Sasha' Kondrashov

266

การใช้ตัวดำเนินการกระจาย ES2015 :

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);

หรือถ้าคุณไม่ต้องการผลลัพธ์:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));

หรือใช้โอเปอเรเตอร์ ES2015 Array.from :

Array.from(...)

const res = Array.from(Array(10)).map((_, i) => {
  return i * 10;
});

// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);

โปรดทราบว่าหากคุณเพียงแค่ต้องสตริงซ้ำคุณสามารถใช้ String.prototype.repeat

console.log("0".repeat(10))
// 0000000000

26
ดีกว่า:Array.from(Array(10), (_, i) => i*10)
Bergi

6
นี่ควรเป็นคำตอบที่ดีที่สุด ดังนั้น ES6! ยอดเยี่ยมมาก!
Gergely Fehérvári

3
หากคุณไม่ต้องการตัววนซ้ำ (i) คุณสามารถยกเว้นทั้งคีย์และค่าเพื่อทำสิ่งนี้:[...Array(10)].forEach(() => console.log('looping 10 times');
Sterling Bourne

9
ดังนั้นคุณจัดสรรอาร์เรย์ทั้งหมดขององค์ประกอบ N เพื่อทิ้งมันไปเหรอ?
Kugel

2
มีใครพูดถึงความคิดเห็นก่อนหน้าโดย Kugel บ้าง ฉันสงสัยในสิ่งเดียวกัน
Arman

37
for (let i of Array(100).keys()) {
    console.log(i)
}

ใช้งานได้ดีมาก! แต่ค่อนข้างน่าเกลียดในแง่ที่ว่าต้องมีการทำงานพิเศษและนี่ไม่ใช่Arrayกุญแจที่ใช้
ที่

@ที่. จริง แต่ฉันไม่แน่ใจว่ามีคำพ้องความหมายของ haskell สำหรับ[0..x]JS ในรัดกุมมากกว่าคำตอบของฉัน
zerkms

คุณอาจถูกต้องว่าไม่มีอะไรรัดกุมกว่านี้
ที่

ตกลงฉันเข้าใจว่าทำไมงานนี้ถึงได้ให้ความแตกต่างระหว่างArray.prototype.keysและObject.prototype.keysแต่แน่ใจว่าสับสนทันที
Mark Reed

1
@cchamberlain กับ TCO ใน ES2015 (ไม่ได้ใช้งานที่ใดก็ได้) อาจเป็นข้อกังวลน้อยกว่า แต่แน่นอน :-)
zerkms

29

ฉันคิดว่าทางออกที่ดีที่สุดคือการใช้let:

for (let i=0; i<100; i++) 

ที่จะสร้างiตัวแปรใหม่ (ไม่แน่นอน) สำหรับการประเมินแต่ละส่วนของร่างกายและรับรองว่าiจะมีการเปลี่ยนแปลงเฉพาะในนิพจน์ส่วนเพิ่มในไวยากรณ์ลูปนั้นไม่ใช่จากที่อื่น

ฉันสามารถโกงและสร้างเครื่องกำเนิดของตัวเองได้ อย่างน้อยi++ก็ออกจากสายตา :)

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

คุณควรจะสบายดีด้วย

function* times(n) {
  for (let i = 0; i < x; i++)
    yield i;
}
for (const i of times(5))
  console.log(i);

แต่ฉันไม่ต้องการใช้++โอเปอเรเตอร์หรือมีตัวแปรที่ไม่แน่นอนใด ๆ

จากนั้นตัวเลือกเดียวของคุณคือใช้การเรียกซ้ำ คุณสามารถกำหนดว่าฟังก์ชั่นเครื่องกำเนิดไฟฟ้าโดยไม่ต้องผันแปรiเช่นกัน:

function* range(i, n) {
  if (i >= n) return;
  yield i;
  return yield* range(i+1, n);
}
times = (n) => range(0, n);

แต่นั่นดูเหมือนว่าเกินความจริงสำหรับฉันและอาจมีปัญหาด้านประสิทธิภาพ (เนื่องจากไม่มีการกำจัดหางแบบโทรออกreturn yield*)


1
ฉันชอบตัวเลือกนี้ - ดีและเรียบง่าย!
DanV

2
นี้เป็นเรื่องง่ายและตรงประเด็นและไม่ได้จัดสรรอาร์เรย์เช่นหลายคำตอบข้างต้น
Kugel

@Kugel คนที่สองอาจจัดสรรให้กับกองซ้อนแม้ว่า
Bergi

จุดที่ดีไม่แน่ใจว่าการเพิ่มประสิทธิภาพการโทรแบบหางจะทำงานที่นี่ @Bergi
Kugel

13
const times = 4;
new Array(times).fill().map(() => console.log('test'));

ตัวอย่างนี้จะconsole.log test4 ครั้ง


การสนับสนุนสำหรับการเติมคืออะไร?
Aamir Afridi

2
@AamirAfridi คุณสามารถตรวจสอบส่วนความเข้ากันได้ของเบราว์เซอร์นอกจากนี้ยังมี polyfill ให้: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......


11

คำตอบ: 09 ธันวาคม 2558

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

ตัวอย่างที่ให้ไว้ในคำถามคือบางอย่างที่เหมือนกับ Ruby:

x.times do |i|
  do_stuff(i)
end

การแสดงสิ่งนี้ใน JS โดยใช้ด้านล่างจะอนุญาต:

times(x)(doStuff(i));

นี่คือรหัส:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

แค่นั้นแหละ!

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

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

หรือทำตามตัวอย่างของคำตอบที่ยอมรับได้:

let doStuff = (i) => console.log(i, ' hi'),
  once = times(1),
  twice = times(2),
  thrice = times(3);

once(doStuff);
//0 ' hi'

twice(doStuff);
//0 ' hi'
//1 ' hi'

thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'

หมายเหตุด้านข้าง - การกำหนดฟังก์ชั่นช่วง

คำถามที่คล้ายกัน / เกี่ยวข้องซึ่งใช้การสร้างโค้ดที่คล้ายกันมากโดยพื้นฐานอาจมีฟังก์ชั่น Range ที่สะดวกสบายใน (คอร์) จาวาสคริปต์สิ่งที่คล้ายกับฟังก์ชั่นพิสัยของขีดล่าง

สร้างอาร์เรย์ด้วยตัวเลข n เริ่มต้นจาก x

ขีด

_.range(x, x + n)

ES2015

ทางเลือกสองทาง:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)

การสาธิตโดยใช้ n = 10, x = 1:

> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

ในการทดสอบอย่างรวดเร็วฉันวิ่งโดยที่แต่ละข้างต้นใช้งานล้านครั้งโดยใช้โซลูชันและฟังก์ชั่น doStuff ของเราวิธีการเดิม (Array (n) .fill ()) ได้รับการพิสูจน์เร็วขึ้นเล็กน้อย


8
Array(100).fill().map((_,i)=> console.log(i) );

รุ่นนี้ตอบสนองความต้องการของ OP สำหรับการเปลี่ยนแปลง ลองพิจารณาใช้reduceแทนmap ทั้งนี้ขึ้นอยู่กับกรณีการใช้งานของคุณ

นี่ยังเป็นตัวเลือกถ้าคุณไม่คำนึงถึงการกลายพันธุ์เล็กน้อยในต้นแบบของคุณ

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};

ตอนนี้เราสามารถทำได้

((3).times(i=>console.log(i)));

+1 ถึง arcseldon สำหรับ.fillคำแนะนำ


ลงคะแนนเนื่องจากวิธีการเติมไม่ได้รับการสนับสนุนใน IE หรือ Opera หรือ PhantomJS
morhook

8

นี่เป็นอีกทางเลือกที่ดี:

Array.from({ length: 3}).map(...);

โดยเฉพาะอย่างยิ่งเนื่องจาก @Dave Morse ชี้ให้เห็นในความคิดเห็นคุณสามารถกำจัดmapสายโดยใช้พารามิเตอร์ที่สองของArray.fromฟังก์ชั่นดังนี้:

Array.from({ length: 3 }, () => (...))


2
นี่ควรเป็นคำตอบที่ยอมรับได้! หนึ่งข้อเสนอแนะเล็ก ๆ น้อย ๆ - คุณได้รับฟังก์ชั่นคล้ายแผนที่ที่คุณต้องการใช้ฟรีด้วย Array จาก Array.from({ length: label.length }, (_, i) => (...)) นี้เป็นการบันทึกการสร้างอาร์เรย์ชั่วคราวที่ว่างเปล่าเพื่อเริ่มการโทรไปยังแผนที่
เดฟมอร์ส

7

ไม่ใช่สิ่งที่ฉันจะสอน (หรือเคยใช้ในรหัสของฉัน) แต่นี่เป็นวิธีแก้ปัญหาที่คุ้มค่า codegolf โดยไม่ต้องกลายพันธุ์ตัวแปรไม่จำเป็นต้องใช้ ES6:

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

สิ่งที่พิสูจน์แนวคิดได้น่าสนใจมากกว่าคำตอบที่มีประโยชน์จริงๆ


ไม่Array.apply(null, {length: 10})น่าจะArray(10)ใช่มั้ย
Pavlo

1
@ Pavlo จริง ๆ แล้วไม่ Array (10) จะสร้างอาร์เรย์ที่มีความยาว 10 แต่ไม่มีคีย์ที่กำหนดไว้ในนั้นซึ่งทำให้ forEach สร้างไม่สามารถใช้งานได้ในกรณีนี้ แต่แน่นอนว่ามันง่ายขึ้นถ้าคุณไม่ใช้ forEach ดูคำตอบของ zerkms (ซึ่งใช้ ES6 แม้ว่า!)
doldt

creative @doldt แต่ฉันกำลังมองหาบางสิ่งที่สามารถสอนได้และเรียบง่าย
ที่

5

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

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0

3
ขอบคุณสำหรับการเป็นเสียงแห่งเหตุผลในสิ่งที่ฉันสามารถอธิบายได้ว่าเป็นปาร์ตี้เครื่องรางแลมบ์ดาที่สูงขึ้นเท่านั้น ฉันก็ลงเอยด้วยคำถามและคำตอบที่ตามมาด้วยการโจมตีครั้งแรกบนเส้นทางของ Google ที่ไร้เดียงสาและจิตสำนึกของฉันก็ถูกทำลายอย่างรวดเร็วโดยคำตอบส่วนใหญ่ที่นี่ ของคุณเป็นคนแรกในรายการที่ฉันจะพิจารณาทางออกที่ตรงไปตรงมากับปัญหาที่ตรงไปตรงมา
Martin Devillers

ปัญหาเดียวของเรื่องนี้คือมันเป็น counterintuitive เล็กน้อยถ้าคุณต้องการใช้timesตัวแปรภายในลูป อาจcountdownเป็นการตั้งชื่อที่ดีกว่า มิฉะนั้นคำตอบที่สะอาดและชัดเจนที่สุดในหน้า
Tony Brasunas

3

ตัวอย่าง, ไม่มีกลไกใน ES6 คล้ายกับtimesวิธีการของรูบี้. แต่คุณสามารถหลีกเลี่ยงการกลายพันธุ์โดยใช้การเรียกซ้ำ:

let times = (i, cb, l = i) => {
  if (i === 0) return;

  cb(l - i);
  times(i - 1, cb, l);
}

times(5, i => doStuff(i));

ตัวอย่าง: http://jsbin.com/koyecovano/1/edit?js,console


ฉันชอบวิธีการนี้ฉันรักการสอบถามซ้ำ แต่ฉันรักสิ่งที่ง่ายกว่าในการแสดงลูปผู้ใช้ JavaScript ใหม่
ที่

3

หากคุณยินดีที่จะใช้ห้องสมุดก็มีบ้านพัก_.timesหรือขีดล่าง_.times :

_.times(x, i => {
   return doStuff(i)
})

หมายเหตุนี่จะส่งคืนอาร์เรย์ของผลลัพธ์ดังนั้นมันจึงเป็นเหมือนทับทิมมากขึ้น:

x.times.map { |i|
  doStuff(i)
}

2

ในกระบวนทัศน์การทำงานrepeatมักจะเป็นฟังก์ชั่นวนซ้ำแบบไม่มีที่สิ้นสุด หากต้องการใช้เราจำเป็นต้องมีการประเมินผลที่ขี้เกียจหรือสไตล์การส่งต่อที่ต่อเนื่อง

การทำซ้ำฟังก์ชันที่ประเมินขี้เกียจ

const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

ฉันใช้ thunk (ฟังก์ชั่นที่ไม่มีข้อโต้แย้ง) เพื่อให้ได้การประเมินผลที่ขี้เกียจใน Javascript

การทำซ้ำฟังก์ชั่นด้วยสไตล์การส่งต่ออย่างต่อเนื่อง

const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

CPS ค่อนข้างน่ากลัวในตอนแรก อย่างไรก็ตามมันมักจะเป็นไปตามรูปแบบเดียวกันเสมอ: อาร์กิวเมนต์สุดท้ายคือความต่อเนื่อง (ฟังก์ชั่น) ซึ่งจะเรียกใช้ตัวของมันเอง: k => k(...). โปรดทราบว่า CPS จะเปิดใบสมัครภายในออกคือtake(8) (repeat...)จะกลายเป็นk(take(8)) (...)ที่ถูกนำมาใช้บางส่วนkrepeat

ข้อสรุป

โดยการแยกการทำซ้ำ ( repeat) ออกจากเงื่อนไขการเลิกจ้าง ( take) เราจะได้รับความยืดหยุ่น - แยกความกังวลออกไปจนถึงจุดสิ้นสุดที่ขมขื่น: D


1

ข้อดีของการแก้ปัญหานี้

  • อ่าน / ใช้ง่ายที่สุด (imo)
  • ค่าส่งคืนสามารถใช้เป็นผลรวมหรือเพียงแค่ละเว้น
  • รุ่น ES6 ธรรมดานอกจากนี้ยังเชื่อมโยงไปยังรุ่น typescriptของรหัส

ข้อเสีย - การกลายพันธุ์ เป็นเพียงภายในฉันไม่สนใจบางทีคนอื่นอาจจะไม่

ตัวอย่างและรหัส

times(5, 3)                       // 15    (3+3+3+3+3)

times(5, (i) => Math.pow(2,i) )   // 31    (1+2+4+8+16)

times(5, '<br/>')                 // <br/><br/><br/><br/><br/>

times(3, (i, count) => {          // name[0], name[1], name[2]
    let n = 'name[' + i + ']'
    if (i < count-1)
        n += ', '
    return n
})

function times(count, callbackOrScalar) {
    let type = typeof callbackOrScalar
    let sum
    if (type === 'number') sum = 0
    else if (type === 'string') sum = ''

    for (let j = 0; j < count; j++) {
        if (type === 'function') {
            const callback = callbackOrScalar
            const result = callback(j, count)
            if (typeof result === 'number' || typeof result === 'string')
                sum = sum === undefined ? result : sum + result
        }
        else if (type === 'number' || type === 'string') {
            const scalar = callbackOrScalar
            sum = sum === undefined ? scalar : sum + scalar
        }
    }
    return sum
}

TypeScipt เวอร์ชั่น
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011


0

ที่อยู่ด้านการทำงาน:

function times(n, f) {
    var _f = function (f) {
        var i;
        for (i = 0; i < n; i++) {
            f(i);
        }
    };
    return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
    console.log('in parts: ' + v);
});
times(6, function (v) {
    console.log('complete: ' + v);
});

5
"ที่อยู่ด้านทำงาน" iแล้วใช้ห่วงความจำเป็นด้วยแน่นอน อะไรคือเหตุผลในการใช้งานtimesมากกว่าเก่าธรรมดาfor?
zerkms

var twice = times(2);นำมาใช้ใหม่เช่น
Nina Scholz

เหตุใดจึงไม่ใช้เพียงforสองครั้ง
zerkms

ฉันไม่กลัวที่จะใช้ คำถามคือสิ่งที่จะไม่ใช้ Variabele แต่ผลลัพธ์มักจะเป็นตัวแปรการแคชบางอย่าง
Nina Scholz

1
"เป็นสิ่งที่ไม่ใช้ variabele" --- และคุณยังคงใช้มัน i++- ไม่ชัดเจนว่าการห่อสิ่งที่ยอมรับไม่ได้ในฟังก์ชั่นทำให้ดีขึ้นได้อย่างไร
zerkms

0

เครื่องปั่นไฟ? recursion? ทำไม hatin ถึงอยู่บน mutatin? ;-)

ถ้ามันเป็นที่ยอมรับตราบใดที่เรา "ซ่อน" มันก็แค่ยอมรับการใช้โอเปอเรเตอร์ unary และเราสามารถทำให้สิ่งต่าง ๆ ง่ายขึ้น :

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

เหมือนทับทิม

> (3).times(console.log)
0
1
2

2
Thumbs up: "ทำไม hatin มาก 'บน mutatin'"
Sarreph

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

1
@mrm คือ "patching ลิง" นี่ไม่ใช่กรณีของการขยายใช่ไหม โอบกอด & ขยาย :)
conny

ไม่การเพิ่มฟังก์ชั่นให้กับ Number (หรือ String หรือ Array หรือคลาสอื่น ๆ ที่คุณไม่ได้เขียน) นั้นหมายถึง polyfills หรือ monkey patch - และไม่แนะนำให้ใช้ polyfills อ่านคำจำกัดความของ "monkey patch", "polyfill" และทางเลือกที่แนะนำ "ponyfill" นั่นคือสิ่งที่คุณต้องการ
mrm

ในการขยาย Number คุณต้องทำ: class SuperNumber ขยาย Number {times (fn) {สำหรับ (ให้ i = 0; i <this; i ++) {fn (i); }}}
Alexander

0

ฉันตอบคำตอบของ @Tieme ด้วยฟังก์ชันผู้ช่วย

ใน TypeScript:

export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())

ตอนนี้คุณสามารถเรียกใช้:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']

0

ฉันทำสิ่งนี้:

function repeat(func, times) {
    for (var i=0; i<times; i++) {
        func(i);
    }
}

การใช้งาน:

repeat(function(i) {
    console.log("Hello, World! - "+i);
}, 5)

/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/

iตัวแปรผลตอบแทนจำนวนครั้งจะได้คล้อง - การมีประโยชน์ถ้าคุณจำเป็นต้องโหลดจำนวน x ของภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.