Yinelenen bir işlevin kaç kez çağrıldığını takip edin


62

 function singleDigit(num) {
      let counter = 0
      let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

      if(number <= 9){
          console.log(number)
      }else{
          console.log(number)
          return singleDigit(number), counter += 1
      }
   }
singleDigit(39)

Yukarıdaki kod bir tamsayı alır ve kendi rakamlarıyla çarparak tek bir basamağa indirir.

Örnek 39'dur.

3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.

Konsol günlüğe kaydeder:

27 
14 
4

Özyinelemeli işlevin 3 kez çağrıldığını nasıl takip edebilirim?

Bir sayaç eklemeyi denedim ancak güncelleme başarısız oluyor. Herhangi bir yardım için minnettar olurum


4
.map(Number)çünkü *operatör değerleri yine de sayıya zorlar. ;-)
RobG

4
Birkaç soru: 1) Negatif sayılarla nasıl başa çıkmayı düşünüyorsunuz? Örneğin, sayı -57gerçekten bir -50ve bir -7.. bu şekilde bakıldığında, pozitif bir sayı veren -5x azalması yapacaktır . Ya da sadece negatif işaretini görmek istiyor musunuz , değil de tho gerçekten de negatif. 2) Sıfır içeren sayılarla nasıl başa çıkmayı düşünüyorsunuz? çünkü bu otomatik olarak indirimi sıfırlayacaktır. Bu nedenle, daha fazla sayı geçerseniz, sıfırlama olasılığı artar. Diğer seçenek sıfırları atlamak olacaktır-735577
Pimp Trizkit

3
Yukarıdaki sorularımın özyinelemeyi saymakla değil, sadece bu soruda kullanılan içeriğin bulmaca çözme yönüyle ilgili olduğunun farkındayım. Beni Affet lütfen.
Pezevenk Trizkit

3
Cevabımı beğendiğiniz için gurur duydum, ancak pratik amaçlar için bence stackoverflow.com/a/59570894/1346276 en temiz genel varyant.
phipsgabler

2
@phipsgabler akıllı ve tutarlı bir cevap yazmak için zaman ayıran herkes bir şeyi hak ediyor. Thank you
chs242

Yanıtlar:


25

Bu neredeyse tamamen akademik bir varyanttır, ancak bu amaçla değiştirilmiş bir sabit nokta birleştiriciyi kullanabilirsiniz .

Orijinal işlevinizi biraz kısaltın ve geliştirelim:

function singleDigit(n) {
    let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
    return digitProduct <= 9 ? digitProduct : singleDigit(digitProduct);
}

// singleDigit(123234234) == 0

Bu varyanttan, özyinelemeli çağrıyı etkisiz hale getirebilir ve köpürebiliriz:

function singleDigitF(recur) {
    return function (n) {
        let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
        return digitProduct <= 9 ? digitProduct : recur()(digitProduct);
    };
}

Bu işlev artık bir sabit nokta birleştirici ile kullanılabilir; özellikle aşağıdaki gibi (katı) JavaScript için uyarlanmış bir Y birleştirici uyguladı:

function Ynormal(f, ...args) {
    let Y = (g) => g(() => Y(g));
    return Y(f)(...args);
}

nerede olduğumuzu Ynormal(singleDigitF, 123234234) == 0.

Şimdi hile geliyor. Y birleştiricisinin özyinelemesini çarpanlarına ayırdığımız için, içindeki özyineleme sayısını sayabiliriz:

function Ycount(f, ...args) {
    let count = 1;
    let Y = (g) => g(() => {count += 1; return Y(g);});
    return [Y(f)(...args), count];
}

Düğüm REPL'indeki hızlı bir kontrol şunları sağlar:

> Ycount(singleDigitF, 123234234)
[ 0, 3 ]
> let digitProduct = (n) => [...(n + '')].reduce((x, y) => x * y, 1)
undefined
> digitProduct(123234234)
3456
> digitProduct(3456)
360
> digitProduct(360)
0
> Ycount(singleDigitF, 39)
[ 4, 3 ]

Bu birleştirici şimdi tarzında yazılmış herhangi bir özyinelemeli işlevdeki çağrı sayısını saymak için çalışacaktır singleDigitF.

(Çok sık bir cevap olarak sıfır elde etmek için iki kaynak olduğunu unutmayın: sayısal taşma ( 123345456999999999olma 123345457000000000vb.) Ve girdinin boyutu büyürken bir yerde bir ara değer olarak neredeyse kesinlikle sıfır alacağınız gerçeği.)


6
Downvoter'lara: Bunun size en iyi pratik çözüm olmadığına gerçekten katılıyorum - bu yüzden "tamamen akademik" olarak ön ek yaptım.
phipsgabler

Dürüst olmak gerekirse, bu harika bir çözümdür ve orijinal sorunun regresyon / matematik tipi için tamamen uygundur.
Sheraff

73

İşlev tanımınıza bir sayaç bağımsız değişkeni eklemelisiniz:

function singleDigit(num, counter = 0) {
    console.log(`called ${counter} times`)
    //...
    return singleDigit(number, counter+1)
}
singleDigit(39)

6
müthiş. Görünüşe göre
sayacım

7
@ chs242 kapsam kuralları, işlevde bildirilmesinin her çağrıda yeni bir tane oluşturacağını belirler. stackoverflow.com/questions/500431/…
Taplar

10
@ chs242 işlev içinde bildirdiğinizden değil. Teknik olarak tüm varsayılan parametreler de bunu yapıyor - sizin durumunuzda, fonksiyonun tekrar tekrar çağrıldığı zaman değerin hiç aktarılmamış olması basitçe. ae fonksiyon çalışır her zaman counteriçin hurdaya ve seti alacağı 0, sürece Sheraff yaptığı gibi açık bir şekilde özyinelemeli arama ters taşırlar. AesingleDigit(number, ++counter)
zfrisch

2
right @zfrisch Bunu şimdi anlıyorum. Açıklamak için zaman ayırdığınız için teşekkürler
chs242

35
Lütfen değiştirin ++counteriçin counter+1. İşlevsel olarak eşdeğerdirler, ancak ikincisi amacı daha iyi belirtir, (gereksiz yere) mutasyona ve parametreye sahip değildir ve yanlışlıkla art arda artma olasılığına sahip değildir. Ya da daha iyisi, bu bir kuyruk çağrısı olduğundan, bunun yerine bir döngü kullanın.
BlueRaja - Danny Pflughoeft

37

Geleneksel çözüm, sayıyı başka bir cevap tarafından önerilen işleve parametre olarak iletmektir.

Ancak, js'de başka bir çözüm daha vardır. Birkaç cevap daha, özyinelemeli fonksiyonun dışında sayım bildirilmesini önerdi:

let counter = 0
function singleDigit(num) {
  counter++;
  // ..
}

Bu tabii ki işe yarıyor. Ancak bu, işlevi yeniden giriş yapmaz hale getirir (iki kez doğru çağrılamaz). Bazı durumlarda bu sorunu görmezden gelebilir ve singleDigitiki kez aramadığınızdan emin olabilirsiniz (javascript tek iş parçacıklıdır, bu yüzden yapmak çok zor değildir), ancak singleDigitdaha sonra eşzamansız olarak güncellenir ve aynı zamanda hissedilir çirkin.

Çözüm, counterdeğişkeni dışarıda ilan etmek ama global olarak bildirmektir . Javascript'in kapanışları olduğu için bu mümkündür:

function singleDigit(num) {
  let counter = 0; // outside but in a closure

  // use an inner function as the real recursive function:
  function recursion (num) {
    counter ++
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
      return counter            // return final count (terminate)
    }else{
      return recursion(number)  // recurse!
    }
  }

  return recursion(num); // start recursion
}

Bu, global çözüme benzer, ancak her aradığınızda singleDigit(şimdi özyinelemeli bir işlev değildir ), counterdeğişkenin yeni bir örneğini oluşturur .


1
Counter değişkeni yalnızca singleDigitişlev içinde kullanılabilir ve bunu bağımsız değişken imo'sunu geçmeden yapmanın alternatif temiz bir yolunu sunar. +1
AndrewL64

1
Yana recursionartık tamamen izole edilir, son parametre olarak sayaç geçmek tamamen güvenli olmalıdır. İçsel bir fonksiyon yaratmanın gerekli olduğunu düşünmüyorum. Özyinelemenin tek faydası için parametrelere sahip olma fikrinden hoşlanmıyorsanız (kullanıcının bunlarla uğraşabileceği noktayı alıyorum), sonra Function#bindkısmen uygulanan bir işlevle kilitleyin .
customcommander

@customcommander Evet, cevabımın ilk bölümünde özet olarak bahsetmiştim the traditional solution is to pass the count as a parameter. Bu, kapakları olan bir dilde alternatif bir çözümdür. Bazı açılardan takip edilmesi daha kolaydır, çünkü muhtemelen sonsuz sayıda değişken örneği yerine yalnızca bir değişkendir. Bu çözümü bilmek başka bir yolla, izlediğiniz şey paylaşılan bir nesne (benzersiz bir harita oluşturmayı hayal edin) veya çok büyük bir nesne (HTML dizesi gibi) olduğunda yardımcı olur
slebetman

counter--"iki kez doğru
çağrılamaz

1
@MonkeyZeus Bu ne fark eder? Ayrıca, bulmak istediğimiz sayı olduğunu görmek için sayacı başlatmak için hangi sayıyı nasıl bilebilirsiniz?
slebetman

22

Başka bir yaklaşım, tüm sayıları ürettiğiniz için bir jeneratör kullanmaktır.

Son öğe, numaranızın ntek basamaklı bir sayıya indirgenmiş olması ve kaç kez yinelediğinizi saymak için dizinin uzunluğunu okumanız yeterlidir.

const digits = [...to_single_digit(39)];
console.log(digits);
//=> [27, 14, 4]
<script>
function* to_single_digit(n) {
  do {
    n = [...String(n)].reduce((x, y) => x * y);
    yield n;
  } while (n > 9);
}
</script>


Son düşünceler

Fonksiyonunuzda erken dönüş koşulu almayı düşünebilirsiniz . İçinde sıfır ile herhangi sayılar olacak sıfır döndürür.

singleDigit(1024);       //=> 0
singleDigit(9876543210); //=> 0

// possible solution: String(n).includes('0')

Aynısı 1sadece yapılan numaralar için de söylenebilir .

singleDigit(11);    //=> 1
singleDigit(111);   //=> 1
singleDigit(11111); //=> 1

// possible solution: [...String(n)].every(n => n === '1')

Son olarak, yalnızca pozitif tam sayıları kabul edip etmediğinizi netleştirmediniz. Negatif tamsayıları kabul ederseniz, bunları dizelere yayınlamak riskli olabilir :

[...String(39)].reduce((x, y) => x * y)
//=> 27

[...String(-39)].reduce((x, y) => x * y)
//=> NaN

Olası çözüm:

const mult = n =>
  [...String(Math.abs(n))].reduce((x, y) => x * y, n < 0 ? -1 : 1)

mult(39)
//=> 27

mult(-39)
//=> -27

harika. @customcommander bunu çok açık bir şekilde açıkladığınız için teşekkür ederiz
chs242

6

Burada birçok ilginç cevap var. Bence versiyonum ilginç bir alternatif sunuyor.

Gerekli fonksiyonunuzla birkaç şey yaparsınız. Yinelemeli olarak tek bir basamağa azaltırsınız. Ara değerleri günlüğe kaydedersiniz ve yapılan yinelemeli çağrıların sayısını istiyorsunuz. Tüm bunları ele almanın bir yolu, nihai sonucu, atılan adımları ve çağrı sayısını içeren bir veri yapısını döndürecek saf bir işlev yazmaktır:

  {
    digit: 4,
    steps: [39, 27, 14, 4],
    calls: 3
  }

Ardından, isterseniz adımları kaydedebilir veya daha fazla işlem için saklayabilirsiniz.

İşte bunu yapan bir sürüm:

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])

console .log (singleDigit (39))

Biz izlemek stepsama türetmek unutmayın calls. Çağrı sayısını ek bir parametre ile izleyebilsek de, hiçbir şey kazanmıyor gibi görünüyor. Ayrıca map(Number)adımı atlıyoruz - bunlar çarpma ile her durumda sayılara zorlanacaktır.

Bu varsayılan stepsparametrenin API'nizin bir parçası olarak gösterilmesiyle ilgili endişeleriniz varsa , aşağıdaki gibi dahili bir işlev kullanarak parametreyi gizlemek yeterince kolaydır:

const singleDigit = (n) => {
  const recur = (n, steps) => 
    n <= 9
      ? {digit: n, steps: [... steps, n], calls: steps .length}
      : recur ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])
  return recur (n, [])
}

Her iki durumda da, rakam çarpımını bir yardımcı fonksiyona çıkarmak biraz daha temiz olabilir:

const digitProduct = (n) => [... (n + '')] .reduce ((a, b) => a * b)

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit (digitProduct(n), [... steps, n])

2
Başka bir harika cevap;) n negatif olduğunda , ( ) digitProductdöneceğini lütfen unutmayın . Bu nedenle, mutlak bir n değeri kullanmak veya azaltmanızın başlangıç ​​değeri olarak kullanmak isteyebilirsiniz . NaN-39 ~> ('-' * '3') * '9'-11
customcommander

@customcommander: aslında, o {"digit":-39,"steps":[-39],"calls":0}zamandan beri geri dönecek -39 < 9. Bu bazı hata denetimi ile yapabileceğini kabul ederken: parametre bir sayı mı? - pozitif bir tamsayı mı? - vs. Bunu eklemek için güncelleme yapacağımı sanmıyorum. Bu algoritmayı yakalar ve hata işleme genellikle kişinin kod tabanına özgüdür.
Scott Sauyet

6

Sadece kaç kez azaldığını saymaya çalışıyorsanız ve özellikle özyineleme konusunda umursamıyorsanız ... özyinelemeyi kaldırabilirsiniz. Aşağıdaki kod, Orijinal Posta'ya sadık kalmaya devam etmediği için sadık kalır num <= 9. Bu nedenle, singleDigit(8)sahip olacak count = 0ve singleDigit(39)olacak count = 3sadece OP ve kabul cevap sergilediklerini gibi:

const singleDigit = (num) => {
    let count = 0, ret, x;
    while (num > 9) {
        ret = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            ret *= x;
        }
        num *= ret;
        count++;
        console.log(num);
    }
    console.log("Answer = " + num + ", count = " + count);
    return num;
}

9 veya daha az sayıları işlemek gereksizdir (yani. num <= 9). Ne yazık ki OP kodu num <= 9sayılmasa bile işleyecektir . Yukarıdaki kod hiç işlemeyecek veya sayılmayacaktır num <= 9. Sadece geçiyor.

Kullanmamayı tercih ediyorum .reduceçünkü gerçek matematiği yürütmek çok daha hızlıydı. Ve benim için anlaşılması daha kolay.


Hız hakkında daha fazla düşünme

İyi kodun da hızlı olduğunu hissediyorum. Bu tür bir indirgeme kullanıyorsanız (numerolojide çok kullanılır) büyük miktarda veri üzerinde kullanmanız gerekebilir. Bu durumda, hız en önemlisi olacaktır.

Her ikisini de kullanmak .map(Number)ve console.log(her azaltma adımında) yürütmek çok uzun ve gereksizdir. Sadece .map(Number)OP'den silmek yaklaşık 4.38x hızlandırdı. Silme console.logo kadar çok hızlandı ki düzgün test etmek neredeyse imkansızdı (Ben beklemek istemedim).

Yani, benzer customcommander 'ın cevabı, kullanmayan .map(Number)ne console.logve sonuçları bir diziye bastırıyor ve kullanma .lengthiçin countçok daha hızlıdır. Ne yazık ki customcommander'ın cevabı için bir jeneratör fonksiyonu kullanmak gerçekten çok yavaş (bu cevap OP olmadan yaklaşık 2.68x daha yavaş .map(Number)ve console.log)

Ayrıca, kullanmak yerine .reducegerçek matematiği kullandım. Bu tek değişiklik tek başına fonksiyonumun versiyonunu 3.59 kat artırdı.

Son olarak, özyineleme daha yavaştır, yığın alanı kaplar, daha fazla bellek kullanır ve kaç kez "tekrarlayabileceği" sınırına sahiptir. Ya da, bu durumda, tam indirgeme işlemini tamamlamak için kaç azaltma aşaması kullanabilir? Yinelemenizi yinelemeli döngülere yaymak, hepsini yığın üzerinde aynı yerde tutar ve bitirmek için kaç azaltma adımının kullanılabileceği konusunda teorik bir sınırlama yoktur. Böylece, bu işlevler, yalnızca yürütme süresi ve bir dizinin ne kadar uzun olabileceği ile sınırlı olan hemen hemen her boyutta tamsayıyı "azaltabilir".

Tüm bunları akılda tutarak ...

const singleDigit2 = (num) => {
    let red, x, arr = [];
    do {
        red = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            red *= x;
        }
        num *= red;
        arr.push(num);
    } while (num > 9);
    return arr;
}

let ans = singleDigit2(39);
console.log("singleDigit2(39) = [" + ans + "],  count = " + ans.length );
 // Output: singleDigit2(39) = [27,14,4],  count = 3

Yukarıdaki fonksiyon son derece hızlı çalışır. Bu OP den (olmadan hızlı 3.13x hakkındadır .map(Number)ve console.logdaha hızlı) ve 8.4x hakkında customcommander 'ın cevabı. OP'den silme işleminin console.logher azaltma adımında bir sayı üretmesini engellediğini unutmayın . Bu nedenle, bu sonuçları bir dizi haline getirme ihtiyacı.

PT


1
Bu cevapta çok fazla eğitim değeri var, bunun için teşekkürler. I feel good code is also fast.Ben kod kalitesi söyleyebilirim etti gereksinimleri önceden tanımlanmış bir dizi karşı ölçülecek. Performans onlardan biri değilse, o zaman herkesin anlayabileceği kodu "hızlı" kodla değiştirerek hiçbir şey kazanamazsınız. Artık kimsenin anlayamadığı noktaya kadar performans göstermesi için yeniden düzenlendiğini gördüğüm kod miktarına inanmazsınız (bir nedenden dolayı en uygun kod da belgelenmez;). Son olarak, tembel oluşturulan listelerin kişinin talep üzerine ürün tüketmesine izin verdiğini unutmayın.
customcommander

Teşekkür ederim sanırım. IMHO, nasıl yapılacağına dair gerçek matematiği okumak benim için anlaşılması daha kolaydı .. burada çoğu cevapta gördüğüm ifadelerden, [...num+''].map(Number).reduce((x,y)=> {return x*y})hatta [...String(num)].reduce((x,y)=>x*y)ifadelerden daha kolaydı . Yani, bana göre, bu, her bir yinelemede neler olup bittiğini daha iyi anlama ve çok daha hızlı bir avantaj sağladı . Evet, küçültülmüş kodu (yeri vardır) okumak çok zor. Ancak bu durumlarda genellikle bilinçli bir şekilde okunabilirliğini önemsemekle kalmaz, sadece kesip yapıştırmanın ve devam etmenin nihai sonucudur.
Pezevenk Trizkit

JavaScript'in tamsayı bölümü yok, bu yüzden C'nin eşdeğerini yapabilirsiniz digit = num%10; num /= 10;? num - xBölme işleminden önce sondaki basamağı çıkarmak için yapmanız gereken , JIT derleyicisini kalanını almak için yaptığı bölümden ayrı bir bölüm yapmaya zorlayacaktır.
Peter Cordes

Ben öyle düşünmüyorum. Bunlar vars (JS'nin hiçbiri yok int). Bu nedenle, gerekirse bir şamandıraya n /= 10;dönüşecektir n. num = num/10 - x/10denklemin uzun biçimi olan bir şamandıraya dönüştürebilir. Bu nedenle, num = (num-x)/10;bir tamsayı tutmak için yeniden düzenlenmiş sürümünü kullanmak zorundayım . Ayrıca, digit = num%10; num /= 10;iki ayrı ifade ve dolayısıyla iki ayrı bölme işlemi var. C'yi kullandığımdan beri bir süredir, ama orada da doğru olduğunu düşündüm.
Pezevenk Trizkit

6

Neden console.countfonksiyonunuzda bir çağrı yapmıyorsunuz ?

Düzenleme: Snippet'i tarayıcınızda denemek için:

function singleDigit(num) {
    console.count("singleDigit");

    let counter = 0
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
        console.log(number)
    }else{
        console.log(number)
        return singleDigit(number), counter += 1
    }
}
singleDigit(39)

Chrome 79 ve Firefox 72'de çalışıyorum


console.count, işlev her çağrıldığında sayaç sıfırlandığından yardımcı olmaz (yukarıdaki yanıtlarda açıklandığı gibi)
chs242

2
Sorununuzu Chrome ve Firefox'ta çalıştırarak anlamıyorum,
cevabıma

6

Bunun için kapatma kullanabilirsiniz.

Sadece counterfonksiyonun kapanışında saklayın .

İşte örnek:

function singleDigitDecorator() {
	let counter = 0;

	return function singleDigitWork(num, isCalledRecursively) {

		// Reset if called with new params 
		if (!isCalledRecursively) {
			counter = 0;
		}

		counter++; // *

		console.log(`called ${counter} times`);

		let number = [...(num + "")].map(Number).reduce((x, y) => {
			return x * y;
		});

		if (number <= 9) {
			console.log(number);
		} else {
			console.log(number);

			return singleDigitWork(number, true);
		}
	};
}

const singleDigit = singleDigitDecorator();

singleDigit(39);

console.log('`===========`');

singleDigit(44);


1
Ancak bu şekilde sayaç bir sonraki çağrıda saymaya devam eder, her ilk çağrıda sıfırlanması gerekir. Zor bir soruya yol açar: özyinelemeli bir işlevin farklı bir bağlamdan, bu durumda global ve işlevin çağrılması nasıl söylenir.
RobG

Bu sadece bir düşünce bulmak için bir örnektir. Kullanıcıdan ihtiyaçlarını sorarak modifiye edilebilir.
Kholiavko

@RGG Sorunuzu anlamıyorum. Özyinelemeli işlev, bir iç işlev olduğu için kapağın dışında çağrılamaz. Dolayısıyla, bağlamı ayırt etmek için bir olasılık veya ihtiyaç yoktur çünkü tek bir olası bağlam vardır
slebetman

@slebetman Sayaç asla sıfırlanmaz. Tarafından döndürülen işlev singleDigitDecorator(), her çağrıldığında aynı sayacı artırmaya devam eder.
customcommander

1
@ slebetman — problem, singleDigitDecorator tarafından döndürülen işlevin tekrar çağrıldığında sayacını sıfırlamamasıdır. Bu, sayacın ne zaman sıfırlanacağını bilmesi gereken işlevdir, aksi takdirde her kullanım için yeni bir işlev örneği gerekir. Function.caller için olası bir kullanım örneği ? ;-)
RobG

1

İşte sayacı basitleştirmek için bir sarmalayıcı işlevi kullanan bir Python sürümü, slebetman'ın cevabının önerdiği gibi - Bunu sadece temel fikir bu uygulamada çok açık olduğu için yazıyorum:

from functools import reduce

def single_digit(n: int) -> tuple:
    """Take an integer >= 0 and return a tuple of the single-digit product reduction
    and the number of reductions performed."""

    def _single_digit(n, i):
        if n <= 9:
            return n, i
        else:
            digits = (int(d) for d in str(n))
            product = reduce(lambda x, y: x * y, digits)
            return _single_digit(product, i + 1)

    return _single_digit(n, 0)

>>> single_digit(39)
(4, 3)

1
Python, ben böyle bir şey tercih ediyorum bu .
phipsgabler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.