如何在TypeScript中获取随机枚举?

33

如何从枚举中获取一个随机项?

enum Colors {
  Red, Green, Blue
}

function getRandomColor(): Color {
  // return a random Color (Red, Green, Blue) here
}
16个回答

35
在从其他解决方案和 "keyof" 关键字中获得了许多灵感之后,这里是一个返回类型安全的随机枚举的通用方法。
function randomEnum<T>(anEnum: T extends object): T[keyof T] {
  const enumValues = Object.keys(anEnum)
    .map(n => Number.parseInt(n))
    .filter(n => !Number.isNaN(n)) as unknown as T[keyof T][]
  const randomIndex = Math.floor(Math.random() * enumValues.length)
  const randomEnumValue = enumValues[randomIndex]
  return randomEnumValue;
}

像这样使用:

interface MyEnum {X, Y, Z}
const myRandomValue = randomEnum(MyEnum) 

myRandomValue 的类型将是 MyEnum


1
你可以使用 Object.values(anEnum).map(n => +n) 替代 Object.keys(anEnum).map(n => Number.parseInt(n)) 吗?枚举的键都是字符串,而其值是数字和字符串的混合(数字是实际的枚举值,字符串是它的键)。例如,给定 enum Bool { FALSE, TRUE }Object.keys(Bool) 将返回 ["0", "1", "FALSE", "TRUE"](全部为字符串),但 Object.values(Bool) 将返回 ["FALSE", "TRUE", 0, 1](数字和字符串)。 - chharvey
2
嗯,对我没用,这是针对数字枚举的吗? - bitwit
1
这段代码无法编译,有很多错误: 1)类型为'T'的参数无法分配给类型为'object'的参数 2)类型为'NumberConstructor'的属性'parseInt'不存在。您需要更改目标库吗?尝试将“lib”编译器选项更改为“es2015”或更高版本。 3)类型为'NumberConstructor'的属性'isNaN'不存在。您需要更改目标库吗?尝试将“lib”编译器选项更改为“es2015”或更高版本。 - Roberto Pegoraro
可能是 Kotlin 改变了它们的语法,我会看一下并进行调整。 - Steven Spungin
刚刚尝试了最新版本的Angular。对于普通枚举(具有数字值),它可以工作,但是当我创建一个带有字符串值的枚举时,就会出现问题,例如 enum Stuff {A="A", B="B"}。编辑:Kabir的答案可行。 - Panossa
为什么需要过滤不是数字的元素?你可以只将键的长度除以2,然后在该范围内选择一个随机索引。 - mockingjay

29

大多数上面的答案都返回枚举键,但我们真正关心的不是枚举值吗?

如果您正在使用lodash,那么这实际上非常简单:

_.sample(Object.values(MyEnum))

如果你不使用lodash,或者你希望这个函数独立存在,我们可以通过修改@Steven Spungin的答案来实现类型安全的随机枚举:

function randomEnum<T>(anEnum: T): T[keyof T] {
  const enumValues = (Object.values(anEnum) as unknown) as T[keyof T][];
  const randomIndex = Math.floor(Math.random() * enumValues.length);
  return enumValues[randomIndex];
}

你的 Lodash 示例中的 myEnum 是什么? - vivekmore
2
Object.values(myEnum) 函数返回了一个混合了字符串和数字的数组,例如 ['Red', 'Green', 'Blue', 0, 1, 2]。这在上述 Colors 枚举中是非常奇怪的。而 Object.keys 函数也会返回类似的结果,只不过其中的数字也会被转换成字符串:['Red', 'Green', 'Blue', '0', '1', '2'] - Lewy Blue

6

所以对我来说,没有任何答案可用,最终我做成了这样:

有以下枚举类型:

export enum Jokenpo {
  PAPER = 'PAPER',
  ROCK = 'ROCK',
  SCISSOR = 'SCISSOR',
}

随机选择
const index= Math.floor(Math.random() * Object.keys(Jokenpo).length);
const value= Object.values(Jokenpo)[index];

return Jokenpo[value];

我只是从枚举类型中随机获取一个值,然后将其用于转换为真实值。


6

你觉得这个怎么样,使用es2017中的Object.values函数(不支持IE浏览器,在其他浏览器中的支持较为最新):

function randEnumValue<T>(enumObj: T): T[keyof T] {
  const enumValues = Object.values(enumObj);
  const index = Math.floor(Math.random() * enumValues.length);
  
  return enumValues[index];
}

我认为你需要在T上加上extends object,否则TypeScript会抱怨(至少在启用严格选项的情况下)。抱怨的版本不抱怨的版本上加上了extends object - undefined

4
如果您需要支持字符串或异构枚举,我建议使用以下函数:
const randomEnumKey = enumeration => {
  const keys = Object.keys(enumeration)
    .filter(k => !(Math.abs(Number.parseInt(k)) + 1));
  const enumKey = keys[Math.floor(Math.random() * keys.length)];
  
  return enumKey;
};

获取一个随机值:

const randomEnumValue = (enumeration) => enumeration[randomEnumKey(enumeration)];

或者简单地说:
MyEnum[randomEnumKey(MyEnum)]

Example:

https://stackblitz.com/edit/typescript-random-enum


太棒了!对于我的使用情况,我需要更多的类型安全性,因此我修改了randomEnumKey,如下所示: const randomEnumKey = <T>(enumeration: T): keyof T => { ... https://stackblitz.com/edit/typescript-random-enum-nfk4ab?file=index.ts - Adam D

3

这是我能想到的最好方法,但看起来像是一个hack,并依赖于TypeScript中enum的实现方式,我不确定它是否保证保持不变。

假设有这样一个枚举:

enum Color { Red, Green, Blue }

如果我们使用console.log来输出它,我们将得到以下输出结果。
{
  '0': 'Red',
  '1': 'Green',
  '2': 'Blue',
  Red: 0,
  Green: 1,
  Blue: 2,
}

这意味着我们可以遍历该对象的键并仅获取数字值,如下所示:
const enumValues = Object.keys(Color)
  .map(n => Number.parseInt(n))
  .filter(n => !Number.isNaN(n))

在我们的情况下,enumValues现在是[0, 1, 2]。我们现在只需要随机选择其中一个值。有一个很好的函数实现可以返回两个值之间的随机整数,这正是我们需要的,以便随机选择索引。
const randomIndex = getRandomInt(0, enumValues.length)

现在我们只是随机选择枚举值:
const randomEnumValue = enumValues[randomIndex]

TSLint在你的最后一行给出错误:TS2322:类型“number”不能分配给类型“Color” - jarodsmk
嗯,也许期间有些变化或者我们使用了不同的编译器标志。不幸的是,我目前无法进行测试,而且我也不记得这是哪个版本了。 :/ - Lazar Ljubenović
没事,我正在自己开发一个不同的解决方案 - 我同时使用 SonarTS 和 TSLint,它们都有相当严格的规则。 - jarodsmk

2

编辑:修正了类型问题。

稍微整理一下@ShailendraSingh的答案,如果枚举如问题中所定义,没有任何自定义索引,那么这是一个简单的方法:

enum Color {
  Red, Green, Blue
}
  
function getRandomColor(): Color {
  var key = Math.floor(Math.random() * Object.keys(Color).length / 2);
  return Color[key];
}

请注意,这里返回类型是Color(题目中要求),实际上它是一个枚举索引而不是颜色名称。

TS Lint 给出了错误提示:TS2322: 类型“number”不能赋值给类型“Color” - jarodsmk
2
那不是代码检查工具,那是 TypeScript 本身。 - Lazar Ljubenović
你为什么要在这里除以2? - Aeternus
1
@Aeternus,这是数字枚举(默认的TS枚举类型)实现方式的一个怪癖 - 索引也被视为键。在这个特定的例子中,Object.keys(Color)返回6个键0,1,2,Red,Green,BlueRed,所以我们需要除以二。如果使用字符串枚举,则不会出现此问题,但实现方式会有所不同。 - yoniLavi

1
除了@sarink的回答之外。
import _ from 'lodash';

export function getRandomValueFromEnum<E>(enumeration: { [s: string]: E } | ArrayLike<E>): E {
  return _.sample(Object.values(enumeration)) as E;
}

1
如果你正在使用faker包(我用它进行测试),你可以使用以下方法。 这个解决方案利用arrayElement函数从Colors枚举中随机选择一种颜色。
// Get an array of all values in the Colors enum
const colorValues = Object.values(Colors);

// Use arrayElement from faker to pick a random color from the array
const randomColor = faker.helpers.arrayElement(colorValues);

1
您可以在下面找到代码以完成任务。
enum Colors {
  Red,
  blue,
  pink,
  yellow,
  Orange
}

function getRandomColor(): string {
  // returns the length
  const len = (Object.keys(Colors).length / 2) - 1;
  // calculate random number
  const item = (Math.floor(Math.random() * len) + 0);

  return Colors[item];
}

1
如果您指定了类似于 enum Colors { Red: 100, Blue } 的枚举,这将失败。 - Lazar Ljubenović
4
是的,但我已经根据问题中指定的格式给出了答案,对于那个格式它完全正常工作。 - Shailendra Singh
@LazarLjubenović 看起来你可以这样做:const item = Math.floor(Math.random() * Object.keys(Colors).length); const i2 = Object.keys(Colors)[item]; return Colors[i2]; - Matt Westlake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接