电源清单的最后一位

时间:2018-07-12 11:47:09

标签: javascript arrays math exponent

问题概述:

请注意,尽管插入符号是JS中按位XOR运算符,但我将滥用^中的生命并将其用作幂符号。

获取正整数列表,

[ x_0, x_1, ..., x_n ]

并找到方程式的最后一位

x_0 ^ ( x_1 ^ (... ^ x_n ) ... )

对于其余的问题,我将将此函数称为LD(...)

示例:对于整数a = [2, 2, 2, 2]并给出2 ^ (2 ^ (2 ^ 2)) = 65536的列表,很容易看到LD(a) = 6

请注意,此问题的0 ^ 0 === 1x ^ 0 === 1一致,但与0 ^ x === 0不一致。


我到目前为止所取得的成就

很容易得出结论,x ^ 0 === 1,无论如何。

如果进行一些测试,很容易得出结论,幂的最后几位会“循环”:

LD(2 ^ 1) = 2,
LD(2 ^ 2) = 4,
LD(2 ^ 3) = 8,
LD(2 ^ 4) = 6,
LD(2 ^ 5) = 2, // Notice we've looped from hereon
LD(2 ^ 6) = 4,
LD(2 ^ 7) = 8,
...

因此,如果我们知道某个特定底数(在上面的底数2的示例中为4)在循环中的计数,则可以使用该计数的模数得出最后一位。

例如LD(2 ^ 55) === LD(2 ^ (55 % 4)) === LD(2 ^ 3)

因此,通过一些数学运算,我们可以为每个最后一个数字获得一个不错的数组,其中数组的索引是基数,每个数组的索引是循环长度:

const p = [
  [ 0 ],      // 0^1=0, 0^2=0 ...
  [ 1 ],      // 1^1=1, 1^2=1 ...
  [ 2,4,8,6 ] // 2^1=2, 2^2=4 ...
  [ 3,9,7,1 ] // 3^1=3, 3^2=9 ...
  [ 4,6 ]     
  [ 5 ]       
  [ 6 ]       
  [ 7,9,3,1 ] 
  [ 8,4,2,6 ] 
  [ 9,1 ]     
];

用法示例:LD(3^7) === p[3][7-1 % 4]-请注意,由于每个数组都是基于0的,因此必须从指数中减去1。

所以我们找到了JavaScript:

LD(Math.pow(a,b)) === p[a % 10][(b-1) % p[a % 10].length]

a % 10应该很明显,它仅将基数的最后一位作为我们数组的索引,因为任何非单位都不会影响最后一位。

对于问题开头的[1,2,3]之类的列表,可以将其设为递归。如果列表为空,我们的初始值为1,即x^1 === x,然后反转列表以利用.reduce()方法的累加:

[1,2,3].reduceRight( (a,v) => 
  p[v % 10][(a-1) % p[v % 10].length], 1)

按照以下说明进行操作,如下所示:

  • 首先,a = 1 (initial value), v = 3;然后是p[3 % 10] === p[3] === [ 3,9,7,1 ],然后是[ 3,9,7,1 ][ (1-1) % [ 3,9,7,1 ].length] === [ 3,9,7,1 ][ 0 % 4 ] === 3
  • 然后,a = 3 (last iteration), v = 2;如此p[2] === [ 2,4,8,6 ],如此[ 2,4,8,6 ][ 2 % 4 ] === 8
  • 最后,a = 8, v = 1p[1] === [ 1 ][ 1 ][ 8 % 1 ] === 1

因此,我们得到LD([1, 2, 3 ]) === 1,这很难验证:1 ^ (2 ^ 3) === 1 ^ 8 === 1


问题:

这有效,只要指数不超过10,并且此后没有其他迭代。但是,如果出现问题,请检查。让我解释一下:

假设我们有a = [ 2,2,2,2 ]数组。由于1是我们的初始值,因此列表最初是a = [ 1,2,2,2,2 ]。使用上面的减少量:

  • 第一次迭代a = 1, v = 2(请记住,我们的.reduce1作为初始值):
    • p[2 % 10][(1-1) % p[2 % 10].length]
    • = [ 2,4,8,6 ][0 % 4]
    • = 2
    • 易于通过2 ^ 1 = 2 验证,我们的列表现在为[2,2,2,2]
  • 第二次迭代a = 2, v = 2
    • p[2 % 10][(2-1) % p[2 % 10].length]
    • = [ 2,4,8,6 ][1 % 4]
    • = 4
    • 易于通过2 ^ 2 = 4 验证,我们的列表现在为[4,2,2]
  • 第三次迭代a = 4, v = 2
    • p[2 % 10][(4-1) % p[2 % 10].length]
    • = [ 2,4,8,6 ][3 % 4]
    • = 6
    • 已通过2 ^ 4 = 16 轻松验证,我们的列表现在为[16,2]
  • 第四次迭代,问题显而易见的地方a = 6, v = 2
    • p[2 % 10][(6-1) % p[2 % 10].length]
    • = [ 2,4,8,6 ][5 % 4]
    • = 4
    • 容易被2 ^ 16 = 65536 反对。

如果您研究了一段时间,就会很清楚为什么。最后迭代的第三步,

= [ 2,4,8,6 ][5 % 4] = p[ 2,4,8,6 ][1]

应该是

= [ 2,4,8,6 ][15 % 4] = p[ 2,4,8,6 ][3]

因此给出错误的结果。


问题:

是否有一种方法可以基于前一个指数来捕获仅传递前一次迭代的最后一位而创建的“偏移”?我可以在最后一次迭代中以某种方式通过6传递另一条信息,以使模数正确吗?

因此,不仅仅是返回

p[v % 10][(a-1) % p[v % 10].length)

也许可以返回

[ 
  p[v % 10][fn(a[0], a[1]) % p[v % 10].length],
  **some other clever information here to use in the calculation to make the modulus correct**
]

其中fn(a[0], a[1])使用之前的累积值以及其他一些信息来计算正确的mod值。这不一定是数组,也可以是@aec在注释中指出的对象或元组。

一个(糟糕的)解决方案是跟踪累加器中的上一个迭代(例如,对于最后一步,而不是返回6,我可以返回16并将其用于下一次迭代,这将给出正确的索引)。但是,如果数量很大,那是非常不切实际的!假设上一步的数字为4142623,则计算4142^623并将其继续传递是不现实的。

请注意,我知道还有其他解决方案,但是我很好奇能否更改此代码以在我编写的单个.reduce语句中解决此问题。因此可以通过修改来解决此问题:

array.reduceRight( (a,v) => 
  p[v % 10][(a-1) % p[v % 10].length], 1)

尽管讨论了累加器问题?它几乎可以正常工作,我想我离它有效还有一个窍门!

请注意括号!列表[3, 14, 16]等同于3 ^ (14 ^ 16) !== (3 ^ 14) ^ 16

要检查的一些测试可以针对函数调用LU(array)进行验证,其中array是数字数组:

// Current attempt
const p = [
  [ 0 ],      // 0^1=0, 0^2=0 ...
  [ 1 ],      // 1^1=1, 1^2=1 ...
  [ 2,4,8,6 ], // 2^1=2, 2^2=4 ...
  [ 3,9,7,1 ], // 3^1=3, 3^2=9 ...
  [ 4,6 ],     
  [ 5 ],       
  [ 6 ],       
  [ 7,9,3,1 ], 
  [ 8,4,2,6 ], 
  [ 9,1 ]     
];

// CURRENT ATTEMPT
let LU = array => 
  array.reduceRight( (a,v) => 
    a === 0 ? 1 : p[v % 10][(a-1) % p[v % 10].length]
  , 1);

let LUTest = (array, expected) => 
  console.log(
    (LU(array) === expected ? "Success" : "Failed"), 
    "for", array, "got", LU(array), "expected", expected);
    
LUTest([ 2, 2, 2 ],    6)
LUTest([ 2, 2, 2, 2 ], 6)
LUTest([ 3, 4, 5 ],    1)
LUTest([ 6, 8, 10 ],   6)
LUTest([ 2, 2, 0 ],    2)
LUTest([ 12, 30, 21 ], 6) 
LUTest([ 0, 0 ],       1) // x^0 === 1 
LUTest([ 0 ],          0)  

在这里测试:http://www.wolframalpha.com/widgets/view.jsp?id=56c82ccd658e09e829f16bb99457bcbc

感谢您的阅读!


其他想法:

有了迷你突破!因此,对于任何以指数为底的整数(即x中的x^y),LD(x) === LD(x % 10)。这是因为超过第一个(从右到左)的数字不会影响指数结果的单位数字(例如LD(23 ^ 7) === LD(3 ^ 7)

此外,如const p = [ ...中一样,包含单位值循环的数组数组,所有数字的循环长度都为4的最小公倍数,即所有循环均为1、2或4个数字(例如p[3] === [ 3,9,7,1 ]单位数组的长度为4)。

因此,我们可以得出LD((x % 10) ^ (y % 4)) === LD(x ^ y)的结论。

但是请注意,如果数字是4的倍数,它将变为零。我们大部分时间都不想这样!您也不希望20变成指数侧的0-我们希望x的范围是1到10,y的范围是1到4:

因此,LD((x % 10 || 10) ^ (y % 4 || 4)) === LD(x ^ y)。我们可以使用

处理特殊情况
if (x === 0) { 
  return 0 // 0^anything = 0, including 0^0 for argument's sake.
} else if (y === 0) {
  return 1 // anything ^ 0 = 1, excluding 0^0
} else {
  ...
}

这很有趣!这意味着现在计算LD(x ^ y)是合理的,但是我不确定该如何处理。

3 个答案:

答案 0 :(得分:2)

最后!在进行了一些挖掘之后,我意识到我可以修改最初的想法并获得想要的答案:

let LD = (as) =>
  as.reduceRight((acc, val) =>
    Math.pow(val < 20 ? val : (val % 20 + 20), acc < 4 ? acc : (acc % 4 + 4)) 
  , 1) % 10

测试:

let LD = (as) =>
  as.reduceRight((acc, val) =>
    Math.pow(val < 20 ? val : (val % 20 + 20), acc < 4 ? acc : (acc % 4 + 4)) 
  , 1) % 10

let LDTest = (array, expected) => 
  console.log((LD(array) === expected ? "Success" : "Failed"), 
    "for", array, "got", LD(array), "expected", expected);

LDTest([ 2, 2, 2 ],    6)
LDTest([ 2, 2, 2, 2 ], 6)
LDTest([ 3, 4, 5 ],    1)
LDTest([ 6, 8, 10 ],   6)
LDTest([ 2, 2, 0 ],    2)
LDTest([ 12, 30, 21 ], 6) 
LDTest([ 0, 0 ],       1) // x^0 === 1 
LDTest([ 0 ],          0)  

那为什么modulo 20呢?因为模数10在诸如[ 2,2,2,2 ]的情况下会失去精度,所以当我们进入问题示例中的最后一步时:

  

第四次迭代,问题显而易见。 a = 6, v = 2

     

p[2 % 10][(6-1) % p[2 % 10].length]

     

= [ 2,4,8,6 ][5 % 4]

     

= 4

     

容易被2 ^ 16 = 65536驳斥。

通过简单地允许最多20的倍数,我们就可以为数组的每个计数以及p的长度(为10)提供LCM(最低公倍数)。(LCM([1 ,2,4,10])=== 20)。

但是,由于指数现在从未高于40^8(大约6万亿),并且在下一次迭代中取4的模,因此我们可以简单地做指数并分别返回答案时间。

当然要在最后一种情况下获得数字,我们需要取10为模以返回最后一个数字。


这里还有一些我不理解的东西。

我们允许模数值下的任何值保留给三元运算符。例如,对于指数,prev < 4 ? prev : (prev % 4 + 4)。但是,我最初认为这是prev === 0 ? 0 : (prev % 4 + 4)

这是因为零的指数与模的其他倍数没有相同的结尾数字,它始终等于1(x ^ 0 === 1)。因此,通过加4,我们得到的值确实具有相同的最后一位数字,而通过不加零,对于零指数,我们仍然得到1。

为什么必须使用prev < 4 ? prev : (prev % 4 + 4)才能使答案正确?为什么例如prev = 3需要为3而不是像其他的一样添加4?

答案 1 :(得分:1)

我认为您的自上而下方法有些缺陷,因为与LD(b)不同,LD(a^b)不是a的唯一决定因素-即全部 b的数字会影响LD(a^b)的值。因此,使用自顶向下的算法,只有在末尾计算LD 才有意义,这有点不合理。


这并不是说您得出的公式是无用的-恰恰相反。

通过查看内部索引,我们可以发现(b - 1) % p[a % 10].length可能是递归自下而上计算的。那么L = p[a % 10].length的可能值是什么? 1、2和4。I = (b - 1) % L的相应可能值为:

  • 对于L = 1来说,I = 0很小。
  • 对于L = 2,如果I = 0为奇数,则b。让b = c ^ d,并注意如果b为奇数或c为奇数,则d = 0为奇数,否则为偶数。因此,如果我们偷看数组中的下一个数字,这种情况也是微不足道的。
  • 对于L = 4I只是基数4 b - 1的最后一位(称为LD4)。我们可以计算一个类似的数字表,并像以前一样处理数组的其余部分:

    // base 4 table
    const q = [
       [0],  // 0^1=0 ...
       [1],  // 1^1=1 ...
       [2, 0, 0, 0, ..... ], // infinitely recurring
       [3, 1], // 3^1=3, 3^2=1, 3^3=3, 3^4=1 ...
    ];
    

啊。好吧,既然案例很少,那么一些if子句就不会受到伤害:

LD2(a) | LD2(a^b)
----------------------------
0      | 1 if b = 0, else 0
1      | 1 always

LD4(a) | LD4(a^b)
----------------------------------------
0      | 1 if b = 0, else 0
1      | 1 always
2      | 1 if b = 0, 2 if b = 1, else 0
3      | 3 if b odd, else 1

假设0^0 = 1。另外,LD4(b - 1) = LD4(b + 3)


代码:

function isEnd(a, i)
{
   return (i > a.length - 1);
}

function isZero(a, i)
{
   if (!isEnd(a, i))
      if (a[i] == 0)
         return !isZero(a, i+1);
   return false;
}

function isUnity(a, i)
{
   if (isEnd(a, i) || a[i] == 1)
      return true;
   return isZero(a, i+1);
}

function isOdd(a, i)
{
   if (isEnd(a, i) || a[i] % 2 == 1)
      return true;
   return isZero(a, i+1);
}

function LD2(a, i)
{
   if (isEnd(a, i) || a[i] % 2 == 1)
      return 1;
   return isZero(a, i+1) ? 1 : 0;
}

function LD4(a, i)
{
   if (isEnd(a, i)) return 1;
   switch (a[i] % 4) {
      case 0: return isZero(a, i+1) ? 1 : 0;
      case 1: return 1;
      case 2: 
         if (isZero(a, i+1)) return 1;
         if (isUnity(a, i+1)) return 2;
         return 0;
      case 3: return isOdd(a, i+1) ? 3 : 1;
      default: return -1; // exception?
   }
}

const p = [
   [ 0 ],      // 0^1=0, 0^2=0 ...
   [ 1 ],      // 1^1=1, 1^2=1 ...
   [ 2,4,8,6 ], // 2^1=2, 2^2=4 ...
   [ 3,9,7,1 ], // 3^1=3, 3^2=9 ...
   [ 4,6 ],     
   [ 5 ],      
   [ 6 ],      
   [ 7,9,3,1 ],
   [ 8,4,2,6 ],
   [ 9,1 ]
];

function LD10(a)
{
   let LDa = a[0] % 10;
   if (isZero(a, 1)) return 1; // corner case not present in tables
   const row = p[LDa];
   switch (row.length) {
      case 1: return row[0];
      case 2: return row[1 - LD2(a, 1)];
      case 4: return row[(LD4(a, 1) + 3) % 4];
      default: return -1; // exception?
   }
}

上面的代码现在可以通过所有测试用例。

答案 2 :(得分:-1)

请记住(a^b)%c = (a%c)^b % c

我想知道lastDigit
lastDigit = x^y^z % 10 = (x%10 ^ y ^ z) % 10

p[x % 10]具有x的幂重复模式。图案的长度可以为1、2或4。

如果图案的长度为4,则lastDigit = p[x][(y^z % 4)] = p[x][(y%4)^z %4]

如果我们的问题不是x,y,z,而是x0,x1,x2...xn

lastDigit
 = p[x0][(x1 ^ x2 ... ^ xn-1 ^ xn) %4]
 = p[x0][((x1 ^ x2 ... ^ xn-1)%4 ^ xn) %4]

我认为递归性质现在很明显:

我将使用数字4作为余数,因为它是可以除以1、2和4的最小数字,对于它们而言,模式也相对容易找到。

const p = [
  [ 0 ],	  // 0^1=0, 0^2=0 ...
  [ 1 ],	  // 1^1=1, 1^2=1 ...
  [ 2,4,8,6 ], // 2^1=2, 2^2=4 ...
  [ 3,9,7,1 ], // 3^1=3, 3^2=9 ...
  [ 4,6 ],	 
  [ 5 ],	   
  [ 6 ],	   
  [ 7,9,3,1 ], 
  [ 8,4,2,6 ], 
  [ 9,1 ]	 
];

//returns x^y % 4
function powMod4(x, y) {
	switch (x % 4) {
		case 0:
			return 0;
		case 1:
			return 1;
		case 2:
			return y == 1 ? 2 : 0;
		case 3:
			return (y % 2) == 0 ? 1 : 3;
	}
}

function foo(arr) {
	arr = arr.map((it, i, list) => {
		if (i + 1 < list.length && list[i + 1] === 0) {
			return 1;
		} else {
			return it;
		}
	}).filter(x => x !== 0);

	let firstElem = arr[0] % 10;
	let l = p[firstElem].length

	let resp = arr.slice(1).reduce((acc, v) => {
		if (acc === undefined) {
			return v
		}
		let rem = powMod4(acc, v);
		//console.log(v, acc, " => ", rem);
		return rem;

	}, undefined);


	return p[firstElem][(resp + l - 1) % l];
}

function test(arr, expected) {
  let resp = foo(arr);
	console.log(arr.join("^"), resp, resp == expected ? "ok" : "FAIL")
}

test([3, 4, 5], 1)
test([2, 2, 2, 2], 6)
test([4, 13, 16], 4)
test([3,5,7,10], 3)
test([41,42,43], 1)
test([7,6,21], 1)
test([2,2,2,0], 4)

所以我所做的就是,我放弃了LD功能。相反,我正在尝试找到x1^x2...^xn % p[x0].length。相反,您试图找到x1^x2...^xn % 10